(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'    // 背景模糊程度
});

点赞(0)

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部