(function ($) {
// 默认配置参数
const defaults = {
zoomScale: 2, // 默认备用放大倍数
animationSpeed: 300, // 动画速度 ms
zIndex: 99999, // 层级
backdropColor: 'rgba(0, 0, 0, 0.85)', // 背景颜色
backdropBlur: '15px' // 背景模糊度
};
// 动态注入样式
const style = `
<style id="fullpic-style">
.fullpic-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
display: flex; justify-content: center; align-items: center;
opacity: 0; visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.fullpic-overlay.is-visible {
opacity: 1; visibility: visible;
}
/* Swiper 容器与图片 */
.fullpic-swiper {
width: 100%; height: 100%;
opacity: 0; transform: translateY(50px);
transition: all 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.fullpic-overlay.is-visible .fullpic-swiper {
opacity: 1; transform: translateY(0);
}
.fullpic-slide {
display: flex; justify-content: center; align-items: center;
width: 100%; height: 100%;
}
.fullpic-img-wrap {
display: flex; justify-content: center; align-items: center;
width: 100%; height: 100%; cursor: zoom-in;
}
.fullpic-overlay.is-zoomed .fullpic-img-wrap {
cursor: zoom-out;
}
.fullpic-img-wrap.is-grabbing {
cursor: grabbing !important;
}
.fullpic-img {
max-width: 90%; max-height: 90%; object-fit: contain;
transform-origin: center center;
}
/* 图标按钮基础样式 */
.fullpic-icon-btn {
background: none; border: none; outline: none; cursor: pointer;
display: flex; justify-content: center; align-items: center;
padding: 10px; border-radius: 50%;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.fullpic-icon-btn svg {
width: 24px; height: 24px; fill: currentColor;
}
.fullpic-icon-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* 右上角关闭 */
.fullpic-btn-close {
position: absolute; top: 20px; right: 20px; z-index: 10;
color: #fff; background-color: rgba(0,0,0,0.5);
}
.fullpic-btn-close:hover { background-color: rgba(0,0,0,0.8); }
/* 左右切换 */
.fullpic-btn-prev, .fullpic-btn-next {
position: absolute; top: 50%; transform: translateY(-50%); z-index: 10;
color: #fff; background-color: rgba(0,0,0,0.5); width: 50px; height: 50px;
}
.fullpic-btn-prev { left: 20px; }
.fullpic-btn-next { right: 20px; }
.fullpic-btn-prev:hover, .fullpic-btn-next:hover { background-color: rgba(0,0,0,0.8); }
/* 左下角控制区 */
.fullpic-controls-area {
position: absolute; bottom: 30px; left: 30px; z-index: 10;
display: flex; flex-direction: column; align-items: center;
}
/* 弹出菜单 */
.fullpic-menu {
list-style: none; padding: 0; margin: 0; margin-bottom: 15px;
background: #fff; border-radius: 30px; padding: 10px 5px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
display: flex; flex-direction: column; gap: 5px;
opacity: 0; visibility: hidden; transform: translateY(20px);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.fullpic-menu.is-open {
opacity: 1; visibility: visible; transform: translateY(0);
}
.fullpic-menu li {
display: flex; justify-content: center;
}
.fullpic-menu .fullpic-icon-btn {
color: #333; padding: 8px; width: 40px; height: 40px;
}
.fullpic-menu .fullpic-icon-btn:hover { background-color: #f5f5f5; }
/* 主控按钮 (放大/关闭) */
.fullpic-btn-toggle {
background-color: #fff; color: #000; width: 50px; height: 50px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.fullpic-btn-toggle:hover { background-color: #f0f0f0; transform: scale(1.05); }
</style>
`;
if (!$('#fullpic-style').length) {
$('head').append(style);
}
// 插件核心类
class FullPicViewer {
constructor(elements, options) {
this.elements = $(elements);
this.settings = $.extend({}, defaults, options);
this.images = [];
this.swiper = null;
this.isZoomed = false;
this.menuIsOpen = false;
this.currentScale = 1;
// 拖拽相关状态
this.dragState = {
isDragging: false,
startX: 0,
startY: 0,
translateX: 0,
translateY: 0,
currentX: 0,
currentY: 0
};
this.init();
}
init() {
// 收集所有图片的数据
this.elements.each((index, el) => {
const $el = $(el);
// 优先取 data-src 或 href,回退取 src
const src = $el.data('src') || $el.attr('href') || $el.attr('src') || $el.find('img').attr('src');
if (src) {
this.images.push(src);
// 绑定点击事件
$el.on('click', (e) => {
e.preventDefault();
this.open(index);
});
}
});
}
buildDOM() {
// 如果已存在则先销毁
if ($('.fullpic-overlay').length) {
$('.fullpic-overlay').remove();
}
// 构建 Swiper 的 Slide
let slidesHtml = '';
this.images.forEach(src => {
slidesHtml += `
<div class="swiper-slide fullpic-slide">
<div class="fullpic-img-wrap">
<img src="${src}" class="fullpic-img" draggable="false" />
</div>
</div>
`;
});
// 构建完整 DOM
const dom = `
<div class="fullpic-overlay" style="z-index: ${this.settings.zIndex}; background-color: ${this.settings.backdropColor}; backdrop-filter: blur(${this.settings.backdropBlur}); -webkit-backdrop-filter: blur(${this.settings.backdropBlur});">
<!-- 关闭按钮 -->
<button class="fullpic-btn-close fullpic-icon-btn" aria-label="关闭">
<svg viewBox="0 0 24 24"><path d="m5.001 4.006 7.001 7.001 7-7h.99l.003.004v.983l-7.003 7.003L20 19.005v.99l-.99-.002-7.008-7.007L5 19.99h-.99V20L4 19.99h.01V19l7.003-7.003L4.01 4.993v-.987zm-.991 0h-.005L4.01 4z"></path></svg>
</button>
<!-- Swiper 容器 -->
<div class="swiper-container fullpic-swiper">
<div class="swiper-wrapper">
${slidesHtml}
</div>
<!-- 左右切换按钮 -->
<button class="fullpic-btn-prev fullpic-icon-btn"><svg viewBox="0 0 24 24"><path d="M7.495 11.555 7 12.05 15.01 20h.987l.003-.004v-.985l-7.017-6.967L16 4.989v-.992h-.987z"></path></svg></button>
<button class="fullpic-btn-next fullpic-icon-btn"><svg viewBox="0 0 24 24"><path d="m16.505 11.555.495.496L8.99 20h-.987L8 19.996v-.985l7.017-6.967L8 4.989v-.992h.987z"></path></svg></button>
</div>
<!-- 左下角控制区 -->
<div class="fullpic-controls-area">
<!-- 弹出菜单 -->
<ul class="fullpic-menu">
<li><button class="fullpic-btn-zoom-in fullpic-icon-btn" title="放大"><svg viewBox="0 0 24 24"><path d="M10.703 6.695v2.603h2.604l.698.7v.001l-.7.7h-2.602v2.602l-.7.699h-.001l-.7-.7v-2.6h-2.6L6 9.997l.7-.7h2.602V6.696l.7-.7z"></path><path fill-rule="evenodd" d="M10 2a8 8 0 0 1 6.125 13.144L22 21.01V22l-.005.005h-.98l-5.881-5.872A8 8 0 1 1 10 2m0 1.4a6.6 6.6 0 1 0 0 13.2 6.6 6.6 0 0 0 0-13.2" clip-rule="evenodd"></path></svg></button></li>
<li><button class="fullpic-btn-zoom-out fullpic-icon-btn" title="缩小"><svg viewBox="0 0 24 24"><path d="m13.307 9.298.698.7v.001l-.7.7H6.7L6 9.998l.7-.7z"></path><path fill-rule="evenodd" d="M10 2a8 8 0 0 1 6.125 13.144L22 21.01V22l-.005.005h-.98l-5.881-5.872A8 8 0 1 1 10 2m0 1.4a6.6 6.6 0 1 0 0 13.2 6.6 6.6 0 0 0 0-13.2" clip-rule="evenodd"></path></svg></button></li>
<li><button class="fullpic-btn-move-up fullpic-icon-btn" title="向上"><svg viewBox="0 0 24 24"><path d="M11.558 7.495 12.054 7l7.949 8.01v.987l-.004.003h-.985l-6.967-7.017L4.992 16H4v-.987z"></path></svg></button></li>
<li><button class="fullpic-btn-move-down fullpic-icon-btn" title="向下"><svg viewBox="0 0 24 24"><path d="m11.558 16.505.496.495 7.949-8.01v-.987L19.999 8h-.985l-6.967 7.017L4.992 8H4v.987z"></path></svg></button></li>
<li><button class="fullpic-btn-move-left fullpic-icon-btn" title="向左"><svg viewBox="0 0 24 24"><path d="M7.495 11.555 7 12.05 15.01 20h.987l.003-.004v-.985l-7.017-6.967L16 4.989v-.992h-.987z"></path></svg></button></li>
<li><button class="fullpic-btn-move-right fullpic-icon-btn" title="向右"><svg viewBox="0 0 24 24"><path d="m16.505 11.555.495.496L8.99 20h-.987L8 19.996v-.985l7.017-6.967L8 4.989v-.992h.987z"></path></svg></button></li>
</ul>
<!-- 主控按钮 (放大镜/关闭) -->
<button class="fullpic-btn-toggle fullpic-icon-btn">
<svg class="icon-zoom" viewBox="0 0 24 24"><path d="M10.703 6.695v2.603h2.604l.698.7v.001l-.7.7h-2.602v2.602l-.7.699h-.001l-.7-.7v-2.6h-2.6L6 9.997l.7-.7h2.602V6.696l.7-.7z"></path><path fill-rule="evenodd" d="M10 2a8 8 0 0 1 6.125 13.144L22 21.01V22l-.005.005h-.98l-5.881-5.872A8 8 0 1 1 10 2m0 1.4a6.6 6.6 0 1 0 0 13.2 6.6 6.6 0 0 0 0-13.2" clip-rule="evenodd"></path></svg>
<svg class="icon-close" viewBox="0 0 24 24" style="display:none;"><path d="m5.001 4.006 7.001 7.001 7-7h.99l.003.004v.983l-7.003 7.003L20 19.005v.99l-.99-.002-7.008-7.007L5 19.99h-.99V20L4 19.99h.01V19l7.003-7.003L4.01 4.993v-.987zm-.991 0h-.005L4.01 4z"></path></svg>
</button>
</div>
</div>
`;
$('body').append(dom);
}
bindEvents() {
const $overlay = $('.fullpic-overlay');
const $toggleBtn = $('.fullpic-btn-toggle');
const $imgWrap = $('.fullpic-img-wrap');
// 初始化 Swiper
this.swiper = new Swiper('.fullpic-swiper', {
initialSlide: this.currentIndex,
navigation: {
nextEl: '.fullpic-btn-next',
prevEl: '.fullpic-btn-prev',
},
keyboard: true,
on: {
slideChange: () => {
this.resetZoom();
}
}
});
// 右上角关闭
$('.fullpic-btn-close').on('click', () => {
this.close();
});
// 左下角主按钮切换逻辑
$toggleBtn.on('click', () => {
if (this.isZoomed && this.menuIsOpen) {
this.resetZoom(); // 关闭菜单并恢复原图
} else if (this.isZoomed && !this.menuIsOpen) {
this.openMenu(); // 图片已放大,直接显示菜单
} else {
this.zoomIn(true); // 放大图片并显示菜单
}
});
// 菜单上的放大缩小
$('.fullpic-btn-zoom-in').on('click', () => {
this.currentScale *= 1.2;
this.applyTransform();
});
$('.fullpic-btn-zoom-out').on('click', () => {
this.currentScale /= 1.2;
if (this.currentScale <= 1) {
this.resetZoom();
} else {
this.applyTransform();
}
});
// 菜单上的移动控制 (方向已修正:向上就是图片向上移动 tx-=, 向下就是 ty+= 等)
const moveStep = 50;
$('.fullpic-btn-move-up').on('click', () => this.moveImage(0, -moveStep));
$('.fullpic-btn-move-down').on('click', () => this.moveImage(0, moveStep));
$('.fullpic-btn-move-left').on('click', () => this.moveImage(-moveStep, 0));
$('.fullpic-btn-move-right').on('click', () => this.moveImage(moveStep, 0));
// 图片点击放大缩小 (点击图片放大不弹出菜单)
$imgWrap.on('click', (e) => {
if (this.dragState.isDragging) {
this.dragState.isDragging = false; // 消费掉这次防误触状态
return;
}
if (this.isZoomed) {
this.resetZoom();
} else {
this.zoomIn(false);
}
});
// 图片拖拽逻辑 (兼容鼠标和触摸)
$imgWrap.on('mousedown touchstart', (e) => {
if (!this.isZoomed) return;
// 统一获取鼠标或触摸的坐标
const pointer = e.type.indexOf('touch') === 0 ? e.originalEvent.touches[0] : e;
this.dragState.isDragging = false;
this.dragState.initialX = pointer.clientX; // 用于判断是否产生有效移动
this.dragState.initialY = pointer.clientY;
this.dragState.startX = pointer.clientX - this.dragState.translateX;
this.dragState.startY = pointer.clientY - this.dragState.translateY;
$imgWrap.addClass('is-grabbing');
const onMove = (ev) => {
if (!this.isZoomed) return;
const movePointer = ev.type.indexOf('touch') === 0 ? ev.touches[0] : ev;
// 防抖阈值:移动小于 5 像素则不认为是拖拽
const moveDistance = Math.abs(movePointer.clientX - this.dragState.initialX) + Math.abs(movePointer.clientY - this.dragState.initialY);
if (!this.dragState.isDragging && moveDistance < 5) {
return;
}
this.dragState.isDragging = true;
// 防止手机端默认的滚动或者缩放行为
if (ev.cancelable && ev.type.indexOf('touch') === 0) {
ev.preventDefault();
}
this.dragState.translateX = movePointer.clientX - this.dragState.startX;
this.dragState.translateY = movePointer.clientY - this.dragState.startY;
this.applyTransform();
};
const onUp = () => {
$imgWrap.removeClass('is-grabbing');
document.removeEventListener('touchmove', onMove);
document.removeEventListener('mousemove', onMove);
document.removeEventListener('touchend', onUp);
document.removeEventListener('mouseup', onUp);
};
// 使用原生的 passive: false 绑定方式确保 touchmove 能执行 preventDefault
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('mousemove', onMove, { passive: false });
document.addEventListener('touchend', onUp);
document.addEventListener('mouseup', onUp);
});
}
open(index) {
this.currentIndex = index;
this.buildDOM();
// 延迟一点以触发 CSS 缓动弹出动画
setTimeout(() => {
$('.fullpic-overlay').addClass('is-visible');
this.bindEvents();
}, 10);
$('body').css('overflow', 'hidden'); // 禁止底层滚动
}
close() {
$('.fullpic-overlay').removeClass('is-visible');
setTimeout(() => {
$('.fullpic-overlay').remove();
if (this.swiper) {
this.swiper.destroy();
this.swiper = null;
}
$('body').css('overflow', '');
}, this.settings.animationSpeed);
}
zoomIn(showMenu = false) {
this.isZoomed = true;
this.dragState.translateX = 0;
this.dragState.translateY = 0;
// 动态计算全屏比例
const $activeImg = $('.swiper-slide-active .fullpic-img');
if ($activeImg.length) {
const imgW = $activeImg[0].clientWidth;
const imgH = $activeImg[0].clientHeight;
const winW = window.innerWidth;
const winH = window.innerHeight;
if (imgW > 0 && imgH > 0) {
this.currentScale = Math.max(winW / imgW, winH / imgH);
} else {
this.currentScale = this.settings.zoomScale;
}
} else {
this.currentScale = this.settings.zoomScale;
}
// UI 状态切换
$('.fullpic-overlay').addClass('is-zoomed');
$('.fullpic-btn-prev, .fullpic-btn-next').hide();
if (showMenu) {
this.openMenu();
} else {
this.closeMenu(false);
}
// 禁用 swiper 拖动
if(this.swiper) this.swiper.allowTouchMove = false;
this.applyTransform();
}
openMenu() {
this.menuIsOpen = true;
$('.fullpic-btn-toggle .icon-zoom').hide();
$('.fullpic-btn-toggle .icon-close').show();
$('.fullpic-menu').addClass('is-open');
}
closeMenu(resetIcon = true) {
this.menuIsOpen = false;
$('.fullpic-menu').removeClass('is-open');
if (resetIcon) {
$('.fullpic-btn-toggle .icon-zoom').show();
$('.fullpic-btn-toggle .icon-close').hide();
}
}
resetZoom() {
this.isZoomed = false;
this.currentScale = 1;
this.dragState.translateX = 0;
this.dragState.translateY = 0;
// UI 状态恢复
$('.fullpic-overlay').removeClass('is-zoomed');
$('.fullpic-btn-prev, .fullpic-btn-next').show();
this.closeMenu(true);
// 恢复 swiper 拖动
if(this.swiper) this.swiper.allowTouchMove = true;
this.applyTransform();
}
moveImage(dx, dy) {
if(!this.isZoomed) return;
this.dragState.translateX += dx;
this.dragState.translateY += dy;
this.applyTransform();
}
applyTransform() {
const tx = this.dragState.translateX;
const ty = this.dragState.translateY;
const $activeImg = $('.swiper-slide-active .fullpic-img');
$activeImg.css({
'transform': `translate3d(${tx}px, ${ty}px, 0) scale(${this.currentScale})`,
'transition': this.dragState.isDragging ? 'none' : `transform ${this.settings.animationSpeed}ms cubic-bezier(0.215, 0.610, 0.355, 1)`
});
}
}
// 注册 jQuery 插件
$.fn.fullpic = function (options) {
if (!this.length) return this;
new FullPicViewer(this, options);
return this;
};
})(jQuery);
$('.pc-show .imgbox .pic').fullpic({
zoomScale: 2, // 图片放大倍数
animationSpeed: 300, // 动画时长(ms)
backdropColor: 'rgba(0, 0, 0, 0.85)', // 背景透明度
backdropBlur: '15px' // 背景模糊程度
});
发表评论 取消回复