images-viewer-js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).ImageViewer=e()}(this,function(){"use strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e={exports:{}};return function(t){"undefined"!=typeof self&&self,t.exports=function(){function t(t,e){let n,i=0;return function(...a){const o=Date.now(),s=o-i;s>e?(t.apply(this,a),i=o):(clearTimeout(n),n=setTimeout(()=>{t.apply(this,a),i=o},e-s))}}class e{constructor(t){if(this.defaultOptions={closeOnMaskClick:!1,loop:!0,buttons:{zoomIn:!0,zoomOut:!0,rotateLeft:!0,rotateRight:!0,reset:!0,download:!0,fullscreen:!0,prev:!0,next:!0,close:!0,topClose:!0,thumbnails:!0,info:!0,originalSize:!0},imageInfo:{visible:!1,showName:!0,showDimensions:!0},theme:{viewerBgColor:"rgba(0, 0, 0, 0.4)",toolbarBgColor:"rgba(150, 150, 150, 0.7)",toolbarBorderRadius:"30px",toolbarPadding:"8px 12px",toolbarBottom:"20px",buttonBgColor:"rgba(150, 150, 150, 0.7)",buttonHoverBg:"rgba(200, 200, 200, 0.4)",buttonSize:"50px",buttonFontSize:"20px",buttonBorderRadius:"50%",topCloseBtnSize:"44px",topCloseBtnTop:"20px",topCloseBtnRight:"20px",infoBgColor:"rgba(150, 150, 150, 0.7)",infoBorderRadius:"12px",infoPadding:"10px 15px",infoFontSize:"13px",infoTop:"70px",infoLeft:"20px",zoomIndicatorBg:"rgba(150, 150, 150, 0.7)",zoomIndicatorBorderRadius:"18px",zoomIndicatorPadding:"6px 12px",zoomIndicatorFontSize:"14px",zoomIndicatorTop:"20px",zoomIndicatorLeft:"20px",activeColor:"rgba(100, 150, 255, 0.8)",textColor:"rgba(255, 255, 255, 0.9)",shadowColor:"rgba(0, 0, 0, 0.2)",transitionSpeed:"0.3s"}},this.options={...this.defaultOptions,...t,buttons:{...this.defaultOptions.buttons,...t?.buttons||{}},imageInfo:{...this.defaultOptions.imageInfo,...t?.imageInfo||{}},theme:{...this.defaultOptions.theme,...t?.theme||{}}},this.parseImageOptions(t),0===this.images.length)throw new Error("未提供有效的图片URL");this.currentIndex=0,this.scale=1,this.rotation=0,this.translateX=0,this.translateY=0,this.isDragging=!1,this.startX=0,this.startY=0,this.startTranslateX=0,this.startTranslateY=0,this.isFullscreen=!1,this.imageInfoVisible=this.options.imageInfo.visible,this.imageMetadata=[],this.loadedImages=new Map,this.lastTapTime=0,this.lastScale=1,this.lastTranslateX=0,this.lastTranslateY=0,this.hasPreviousState=!1,this.isToggledState=!1,this.touchState={isDragging:!1,isPinching:!1,initialDistance:null,initialScale:null,initialTranslateX:null,initialTranslateY:null,centerX:null,centerY:null,relativeCenterX:null,relativeCenterY:null,lastTouchTime:0,startX:0,startY:0,startTranslateX:0,startTranslateY:0,minScaleChange:.005,scaleRatio:1,stabilizationThreshold:3,movementCount:0},this.eventListeners=new Map,this.injectStyles(),this.createOptimizedElements(),this.bindEvents(),this.preloadImages(),this.loadCurrentImage(),this.show()}injectStyles(){const t=document.createElement("style");t.id="image-viewer-styles",t.textContent=`\n :root {\n /* 背景相关变量 */\n --viewer-bg-color: ${this.options.theme.viewerBgColor};\n \n /* 工具栏相关变量 */\n --toolbar-bg-color: ${this.options.theme.toolbarBgColor};\n --toolbar-border-radius: ${this.options.theme.toolbarBorderRadius};\n --toolbar-padding: ${this.options.theme.toolbarPadding};\n --toolbar-bottom: ${this.options.theme.toolbarBottom};\n \n /* 按钮相关变量 */\n --button-bg-color: ${this.options.theme.buttonBgColor};\n --button-hover-bg: ${this.options.theme.buttonHoverBg};\n --button-size: ${this.options.theme.buttonSize};\n --button-font-size: ${this.options.theme.buttonFontSize};\n --button-border-radius: ${this.options.theme.buttonBorderRadius};\n \n /* 右上角关闭按钮变量 */\n --top-close-btn-size: ${this.options.theme.topCloseBtnSize};\n --top-close-btn-top: ${this.options.theme.topCloseBtnTop};\n --top-close-btn-right: ${this.options.theme.topCloseBtnRight};\n \n /* 信息栏相关变量 */\n --info-bg-color: ${this.options.theme.infoBgColor};\n --info-border-radius: ${this.options.theme.infoBorderRadius};\n --info-padding: ${this.options.theme.infoPadding};\n --info-font-size: ${this.options.theme.infoFontSize};\n --info-top: ${this.options.theme.infoTop};\n --info-left: ${this.options.theme.infoLeft};\n \n /* 缩放指示器变量 */\n --zoom-indicator-bg: ${this.options.theme.zoomIndicatorBg};\n --zoom-indicator-border-radius: ${this.options.theme.zoomIndicatorBorderRadius};\n --zoom-indicator-padding: ${this.options.theme.zoomIndicatorPadding};\n --zoom-indicator-font-size: ${this.options.theme.zoomIndicatorFontSize};\n --zoom-indicator-top: ${this.options.theme.zoomIndicatorTop};\n --zoom-indicator-left: ${this.options.theme.zoomIndicatorLeft};\n \n /* 通用变量 */\n --active-color: ${this.options.theme.activeColor};\n --text-color: ${this.options.theme.textColor};\n --shadow-color: ${this.options.theme.shadowColor};\n --transition-speed: ${this.options.theme.transitionSpeed};\n }\n\n .image-viewer-container {\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n z-index: 9999;\n opacity: 0;\n transition: opacity var(--transition-speed) ease;\n touch-action: none;\n -webkit-user-select: none;\n user-select: none;\n display: none;\n background-color: var(--viewer-bg-color);\n }\n\n /* 修复图片容器样式 - 确保居中 */\n .image-viewer-image-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n\n /* 修复图片样式 - 确保居中 */\n .image-viewer-image {\n position: relative;\n object-fit: contain;\n cursor: grab;\n transition: transform 0.1s ease-out, opacity var(--transition-speed) ease;\n transform-origin: center center;\n opacity: 0;\n box-shadow: 0 8px 25px var(--shadow-color);\n border-radius: 4px;\n user-select: none;\n touch-action: none;\n }\n\n .image-viewer-image.loaded {\n opacity: 1;\n }\n\n .image-viewer-image.dragging {\n cursor: grabbing;\n transition: none;\n }\n\n /* 加载指示器 */\n .image-viewer-loading {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background-color: rgba(127, 127, 127, 0.7);\n padding: 20px 30px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n gap: 15px;\n color: var(--text-color);\n font-size: 18px;\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--transition-speed) ease;\n z-index: 3;\n }\n\n .image-viewer-loading.active {\n opacity: 1;\n }\n\n .image-viewer-loading-spinner {\n width: 40px;\n height: 40px;\n border: 4px solid rgba(255, 255, 255, 0.2);\n border-top-color: var(--active-color);\n border-radius: 50%;\n animation: imageViewerSpin 1s linear infinite;\n }\n\n @keyframes imageViewerSpin {\n to {\n transform: rotate(360deg);\n }\n }\n\n /* 右上角关闭按钮样式 */\n .image-viewer-top-close-btn {\n position: absolute;\n top: var(--top-close-btn-top);\n right: var(--top-close-btn-right);\n width: var(--top-close-btn-size);\n height: var(--top-close-btn-size);\n border-radius: 50%;\n background-color: var(--button-bg-color);\n color: var(--text-color);\n border: none;\n font-size: 20px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--transition-speed);\n z-index: 10;\n backdrop-filter: blur(4px);\n box-shadow: 0 2px 8px var(--shadow-color);\n }\n\n .image-viewer-top-close-btn:hover {\n background-color: rgba(255, 50, 50, 0.3);\n transform: scale(1.1);\n }\n\n /* 缩放指示器样式 */\n .image-viewer-zoom-indicator {\n position: absolute;\n top: var(--zoom-indicator-top);\n left: var(--zoom-indicator-left);\n color: var(--text-color);\n background-color: var(--zoom-indicator-bg);\n padding: var(--zoom-indicator-padding);\n border-radius: var(--zoom-indicator-border-radius);\n font-size: var(--zoom-indicator-font-size);\n z-index: 10;\n min-width: 60px;\n text-align: center;\n backdrop-filter: blur(4px);\n box-shadow: 0 2px 8px var(--shadow-color);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n /* 信息栏样式 */\n .image-viewer-image-info {\n position: absolute;\n top: var(--info-top);\n left: var(--info-left);\n color: var(--text-color);\n background-color: var(--info-bg-color);\n padding: var(--info-padding);\n border-radius: var(--info-border-radius);\n font-size: var(--info-font-size);\n z-index: 10;\n max-width: calc(100% - 40px);\n backdrop-filter: blur(4px);\n box-shadow: 0 4px 12px var(--shadow-color);\n display: none;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .image-viewer-image-info.visible {\n display: block;\n animation: imageViewerFadeIn 0.3s ease;\n }\n\n .image-viewer-image-info p {\n margin: 4px 0;\n line-height: 1.4;\n }\n\n .image-viewer-image-info .info-label {\n color: #ddd;\n margin-right: 5px;\n }\n\n .image-viewer-shortcuts-title {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n font-weight: bold;\n margin-bottom: 5px;\n }\n\n @keyframes imageViewerFadeIn {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n /* 图片计数器 */\n .image-viewer-image-counter {\n position: absolute;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n color: var(--text-color);\n background-color: var(--info-bg-color);\n padding: 6px 12px;\n border-radius: 18px;\n font-size: 14px;\n z-index: 10;\n backdrop-filter: blur(4px);\n box-shadow: 0 2px 8px var(--shadow-color);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n /* 导航按钮 */\n .image-viewer-nav-buttons {\n position: absolute;\n top: 50%;\n left: 0;\n right: 0;\n transform: translateY(-50%);\n display: flex;\n justify-content: space-between;\n pointer-events: none;\n z-index: 5;\n padding: 0 10px;\n }\n\n .image-viewer-nav-btn {\n width: var(--button-size);\n height: var(--button-size);\n border-radius: 50%;\n background-color: var(--button-bg-color);\n color: var(--text-color);\n border: none;\n font-size: 24px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n pointer-events: auto;\n opacity: 0.9;\n backdrop-filter: blur(4px);\n box-shadow: 0 2px 8px var(--shadow-color);\n z-index: 6;\n }\n\n .image-viewer-nav-btn:hover {\n background-color: var(--button-hover-bg);\n opacity: 1;\n transform: scale(1.1);\n }\n\n .image-viewer-nav-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n transform: none;\n }\n\n /* 工具栏样式 */\n .image-viewer-toolbar {\n position: absolute;\n bottom: var(--toolbar-bottom);\n left: 50%;\n transform: translateX(-50%);\n background-color: var(--toolbar-bg-color);\n backdrop-filter: blur(12px);\n padding: var(--toolbar-padding);\n border-radius: var(--toolbar-border-radius);\n display: flex;\n gap: 2px;\n z-index: 10;\n box-shadow: 0 6px 25px var(--shadow-color);\n max-width: calc(100% - 40px);\n overflow-x: auto;\n overflow-y: hidden;\n border: 1px solid rgba(255, 255, 255, 0.1);\n scrollbar-width: none;\n -ms-overflow-style: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .image-viewer-toolbar::-webkit-scrollbar {\n display: none;\n }\n\n .image-viewer-tool-btn {\n width: var(--button-size);\n height: var(--button-size);\n background-color: transparent;\n border: none;\n color: var(--text-color);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: var(--button-font-size);\n transition: all 0.2s;\n flex-shrink: 0;\n position: relative;\n border-radius: var(--button-border-radius);\n margin: 0 2px;\n z-index: 11;\n }\n\n .image-viewer-tool-btn:hover {\n background-color: var(--button-hover-bg);\n transform: translateY(-2px);\n box-shadow: 0 4px 10px var(--shadow-color);\n }\n\n .image-viewer-tool-btn:active {\n background-color: rgba(255, 255, 255, 0.3);\n transform: translateY(0);\n }\n\n .image-viewer-tool-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* 缩略图容器 */\n .image-viewer-thumbnails-container {\n position: absolute;\n bottom: 90px;\n left: 50%;\n transform: translateX(-50%);\n padding: 10px 15px;\n background-color: var(--toolbar-bg-color);\n backdrop-filter: blur(8px);\n border-radius: 12px;\n display: flex;\n gap: 10px;\n overflow-x: auto;\n scrollbar-width: none;\n -ms-overflow-style: none;\n z-index: 10;\n box-shadow: 0 3px 15px var(--shadow-color);\n max-width: calc(100% - 40px);\n -webkit-overflow-scrolling: touch;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .image-viewer-thumbnails-container::-webkit-scrollbar {\n display: none;\n }\n\n .image-viewer-thumbnail-item {\n width: 70px;\n height: 45px;\n border: 2px solid transparent;\n border-radius: 6px;\n overflow: hidden;\n cursor: pointer;\n flex-shrink: 0;\n transition: all 0.2s;\n z-index: 11;\n }\n\n .image-viewer-thumbnail-item img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .image-viewer-thumbnail-item.active {\n border-color: var(--active-color);\n transform: scale(1.05);\n }\n\n .image-viewer-thumbnail-item:hover {\n transform: scale(1.05);\n }\n\n @media (max-width: 768px) {\n .image-viewer-tool-btn {\n width: 44px;\n height: 44px;\n font-size: 18px;\n }\n\n .image-viewer-toolbar {\n padding: 6px 8px;\n bottom: 15px;\n border-radius: 25px;\n max-width: 95%;\n }\n\n .image-viewer-thumbnails-container {\n bottom: 80px;\n padding: 8px 10px;\n gap: 8px;\n max-width: 95%;\n }\n\n .image-viewer-thumbnail-item {\n width: 60px;\n height: 40px;\n }\n\n .image-viewer-nav-btn {\n width: 44px;\n height: 44px;\n font-size: 20px;\n }\n\n .image-viewer-top-close-btn {\n width: 40px;\n height: 40px;\n top: 15px;\n right: 15px;\n }\n\n .image-viewer-image-info {\n font-size: 12px;\n padding: 8px 12px;\n }\n\n .image-viewer-zoom-indicator,\n .image-viewer-image-counter {\n font-size: 12px;\n }\n }\n\n @media (max-width: 480px) {\n .image-viewer-thumbnails-container {\n max-width: 95%;\n padding: 6px 8px;\n }\n\n .image-viewer-thumbnail-item {\n width: 50px;\n height: 35px;\n }\n\n .image-viewer-toolbar {\n max-width: 95%;\n }\n }\n `,document.head.appendChild(t)}createOptimizedElements(){this.container=document.createElement("div"),this.container.className="image-viewer-container",document.body.appendChild(this.container),this.imageContainer=document.createElement("div"),this.imageContainer.className="image-viewer-image-container",this.container.appendChild(this.imageContainer),this.image=document.createElement("img"),this.image.className="image-viewer-image",this.image.alt="预览图片",this.image.crossOrigin="anonymous",this.imageContainer.appendChild(this.image),this.loading=document.createElement("div"),this.loading.className="image-viewer-loading",this.loading.innerHTML='\n <div class="image-viewer-loading-spinner"></div>\n <div>加载中...</div>\n ',this.imageContainer.appendChild(this.loading),this.options.buttons.topClose&&(this.topCloseBtn=document.createElement("button"),this.topCloseBtn.className="image-viewer-top-close-btn",this.topCloseBtn.textContent="×",this.topCloseBtn.title="关闭预览 (ESC)",this.container.appendChild(this.topCloseBtn)),this.zoomIndicator=document.createElement("div"),this.zoomIndicator.className="image-viewer-zoom-indicator",this.container.appendChild(this.zoomIndicator),this.options.buttons.info&&(this.imageInfoPanel=document.createElement("div"),this.imageInfoPanel.className="image-viewer-image-info "+(this.imageInfoVisible?"visible":""),this.container.appendChild(this.imageInfoPanel)),this.images.length>1&&(this.counter=document.createElement("div"),this.counter.className="image-viewer-image-counter",this.container.appendChild(this.counter)),this.images.length>1&&(this.options.buttons.prev||this.options.buttons.next)&&this.createNavButtons(),this.createToolbar(),this.images.length>1&&this.options.buttons.thumbnails&&this.createThumbnails()}createNavButtons(){const t=document.createElement("div");t.className="image-viewer-nav-buttons",t.addEventListener("click",t=>{t.stopPropagation()}),this.options.buttons.prev&&(this.prevBtn=document.createElement("button"),this.prevBtn.className="image-viewer-nav-btn image-viewer-prev-btn",this.prevBtn.textContent="←",this.prevBtn.title="上一张 (←)",this.prevBtn.addEventListener("click",t=>{t.stopPropagation(),this.prevImage()}),t.appendChild(this.prevBtn)),this.options.buttons.next&&(this.nextBtn=document.createElement("button"),this.nextBtn.className="image-viewer-nav-btn image-viewer-next-btn",this.nextBtn.textContent="→",this.nextBtn.title="下一张 (→)",this.nextBtn.addEventListener("click",t=>{t.stopPropagation(),this.nextImage()}),t.appendChild(this.nextBtn)),this.container.appendChild(t)}createToolbar(){const t=document.createElement("div");if(t.className="image-viewer-toolbar",t.addEventListener("click",t=>{t.stopPropagation()}),this.images.length>1){this.options.buttons.prev&&(this.toolbarPrevBtn=this.createToolButton("←",()=>this.prevImage()),t.appendChild(this.toolbarPrevBtn)),this.options.buttons.next&&(this.toolbarNextBtn=this.createToolButton("→",()=>this.nextImage()),t.appendChild(this.toolbarNextBtn));const e=document.createElement("div");e.style.width="10px",e.style.flexShrink="0",t.appendChild(e)}this.options.buttons.zoomOut&&(this.zoomOutBtn=this.createToolButton("−",()=>this.zoom(-.1)),t.appendChild(this.zoomOutBtn)),this.options.buttons.zoomIn&&(this.zoomInBtn=this.createToolButton("+",()=>this.zoom(.1)),t.appendChild(this.zoomInBtn)),this.options.buttons.rotateLeft&&(this.rotateLeftBtn=this.createToolButton("↺",()=>this.rotate(-90)),t.appendChild(this.rotateLeftBtn)),this.options.buttons.rotateRight&&(this.rotateRightBtn=this.createToolButton("↻",()=>this.rotate(90)),t.appendChild(this.rotateRightBtn)),this.options.buttons.reset&&(this.resetBtn=this.createToolButton("⟳",()=>this.resetTransform()),t.appendChild(this.resetBtn)),this.options.buttons.originalSize&&(this.originalSizeBtn=this.createToolButton("1:1",()=>this.showOriginalSize()),t.appendChild(this.originalSizeBtn)),this.options.buttons.info&&(this.infoBtn=this.createToolButton("ⓘ",()=>this.toggleImageInfo()),t.appendChild(this.infoBtn)),this.options.buttons.download&&(this.downloadBtn=this.createToolButton("↓",()=>this.downloadImage()),t.appendChild(this.downloadBtn)),this.options.buttons.fullscreen&&(this.fullscreenBtn=this.createToolButton("⛶",()=>this.toggleFullscreen()),t.appendChild(this.fullscreenBtn)),this.options.buttons.close&&(this.closeBtn=this.createToolButton("×",()=>this.close()),t.appendChild(this.closeBtn)),this.container.appendChild(t)}createToolButton(t,e){const n=document.createElement("button");n.className="image-viewer-tool-btn";const i=document.createElement("span");return i.textContent=t,n.appendChild(i),n.addEventListener("click",t=>{t.stopPropagation(),e()}),n}createThumbnails(){const t=document.createElement("div");t.className="image-viewer-thumbnails-container",t.addEventListener("click",t=>{t.stopPropagation()}),this.images.forEach((e,n)=>{const i=document.createElement("div");i.className="image-viewer-thumbnail-item "+(0===n?"active":""),i.dataset.index=n;const a=document.createElement("img");a.src=e,a.alt=`缩略图 ${n+1}`,a.crossOrigin="anonymous",a.onerror=()=>{a.src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTEyIDJDMTEuMzcgMiAxMC43NyAyLjAzIDEwLjIzIDIuMDljLS41MSAwLS45Ni40NS0uOTYuOTZzLjQ1Ljk2Ljk2Ljk2Ljk2LS40NS45Ni0uOTYuNDUtLjk2Ljk2LS45NiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0yMCAxMkg0Yy0uNTUgMC0xIC40NS0xIDEgMCAuMTguMDIuMzUuMDcuNTFMMiAxOWMwLjU1IDAgMS0uNDUgMS0xVjVjMC0uNTUuNDUtMSAxLTEgMiAwIDMuOTggLjkgNS41IDIgMi42MyAxLjMgNC4xNyAzLjEgNS41IDMgMiAwIDUgMiA1IDV2MmMwIC41NS40NSAxIDEgMWgxNmMuNTUgMCAxLS40NSAxLTF2LTEyYzAtLjU1LS40NS0xLTEtMXptLTggMTRjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOFMzMC40MiAxNiAyNCAxNnoiLz48L3N2Zz4="},i.appendChild(a),i.addEventListener("click",t=>{t.stopPropagation();const e=parseInt(i.dataset.index);e!==this.currentIndex&&(this.currentIndex=e,this.loadCurrentImage(),this.updateThumbnails())}),t.appendChild(i)}),this.container.appendChild(t)}updateImageTransform(){const t=`\n translate(${this.translateX}px, ${this.translateY}px)\n scale(${this.scale})\n rotate(${this.rotation}deg)\n `;this.image.style.transform=t}fitImageToScreen(t,e){this.scale=1,this.translateX=0,this.translateY=0;const n=this.imageContainer.clientWidth,i=this.imageContainer.clientHeight,a=this.rotation%360;let o=t,s=e;if(90!==a&&270!==a||(o=e,s=t),o>n||s>i){const t=n/o,e=i/s;this.scale=Math.min(t,e)}this.scale=Math.max(.1,this.scale),this.updateImageTransform(),this.updateZoomIndicator()}parseImageOptions(t){this.images=[],"string"==typeof t?this.images=[t]:Array.isArray(t)?this.images=t.filter(t=>"string"==typeof t&&""!==t.trim()):"object"==typeof t&&t.images&&Array.isArray(t.images)&&(this.images=t.images.filter(t=>"string"==typeof t&&""!==t.trim()))}preloadImages(){this.images.forEach((t,e)=>{if(!this.loadedImages.has(t)){const n=new Image;n.crossOrigin="anonymous",n.src=t,n.onload=()=>{this.loadedImages.set(t,n),this.imageMetadata[e]={name:this.extractFileName(t),width:n.width,height:n.height}},n.onerror=()=>{console.error(`图片预加载失败: ${t}`)}}})}extractFileName(t){try{const e=new URL(t).pathname.split("/");let n=e[e.length-1];const i=n.indexOf("?");return i>-1&&(n=n.substring(0,i)),n||"unknown-image"}catch(t){return"unknown-image"}}loadCurrentImage(){const t=this.images[this.currentIndex],e=this.loadedImages.has(t);if(this.hasPreviousState=!1,this.isToggledState=!1,this.images.length>1&&this.counter&&(this.counter.textContent=`${this.currentIndex+1} / ${this.images.length}`),this.updateNavButtons(),this.updateThumbnails(),this.scale=1,this.rotation=0,this.translateX=0,this.translateY=0,e){const e=this.loadedImages.get(t),n=new Image;return n.crossOrigin="anonymous",n.src=e.src,void(n.onload=()=>{this.image.src=n.src,this.image.classList.add("loaded");const t=this.imageMetadata[this.currentIndex];t&&(this.fitImageToScreen(t.width,t.height),this.updateImageInfo()),setTimeout(()=>{this.hideLoading()},300)})}this.showLoading(),this.image.classList.remove("loaded");const n=new Image;n.crossOrigin="anonymous",n.src=t,n.onload=()=>{this.loadedImages.set(t,n),this.imageMetadata[this.currentIndex]={name:this.extractFileName(t),width:n.width,height:n.height},this.image.src=n.src,this.image.classList.add("loaded"),this.fitImageToScreen(n.width,n.height),this.updateImageInfo(),setTimeout(()=>{this.hideLoading()},300)},n.onerror=()=>{this.hideLoading(),alert("图片加载失败")}}updateZoomIndicator(){const t=Math.round(100*this.scale);this.zoomIndicator.textContent=`${t}%`}updateImageInfo(){if(!this.options.buttons.info||!this.imageInfoPanel)return;const t=this.imageMetadata[this.currentIndex];if(!t)return;let e="";this.options.imageInfo.showName&&(e+=`<p><span class="info-label">名称:</span> ${t.name}</p>`),this.options.imageInfo.showDimensions&&(e+=`<p><span class="info-label">尺寸:</span> ${t.width} × ${t.height} px</p>`),e+='\n <div class="image-viewer-shortcuts-title">快捷键</div>\n <p><span class="info-label">放大:</span> + / =</p>\n <p><span class="info-label">缩小:</span> -</p>\n <p><span class="info-label">上一张:</span> ←</p>\n <p><span class="info-label">下一张:</span> →</p>\n <p><span class="info-label">重置:</span> 0</p>\n <p><span class="info-label">全屏:</span> F</p>\n <p><span class="info-label">信息:</span> I</p>\n <p><span class="info-label">关闭:</span> ESC</p>\n ',this.imageInfoPanel.innerHTML=e}toggleImageInfo(){this.options.buttons.info&&this.imageInfoPanel&&(this.imageInfoVisible=!this.imageInfoVisible,this.imageInfoVisible?this.imageInfoPanel.classList.add("visible"):this.imageInfoPanel.classList.remove("visible"))}updateNavButtons(){if(this.images.length<=1)return;const t=!!this.options.loop||this.currentIndex>0,e=!!this.options.loop||this.currentIndex<this.images.length-1;this.prevBtn&&(this.prevBtn.disabled=!t),this.nextBtn&&(this.nextBtn.disabled=!e),this.toolbarPrevBtn&&(this.toolbarPrevBtn.disabled=!t),this.toolbarNextBtn&&(this.toolbarNextBtn.disabled=!e)}updateThumbnails(){if(this.images.length<=1)return;document.querySelectorAll(".image-viewer-thumbnail-item").forEach(t=>{t.classList.remove("active")});const t=document.querySelector(`.image-viewer-thumbnail-item[data-index="${this.currentIndex}"]`);t&&(t.classList.add("active"),t.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"}))}showLoading(){this.loading&&this.loading.classList.add("active")}hideLoading(){this.loading&&this.loading.classList.remove("active")}prevImage(){if(this.images.length<=1)return;let t=this.currentIndex-1;t<0&&(t=this.options.loop?this.images.length-1:0),t!==this.currentIndex&&(this.currentIndex=t,this.loadCurrentImage())}nextImage(){if(this.images.length<=1)return;let t=this.currentIndex+1;t>=this.images.length&&(t=this.options.loop?0:this.images.length-1),t!==this.currentIndex&&(this.currentIndex=t,this.loadCurrentImage())}bindEvents(){this.topCloseBtn&&this.addEvent(this.topCloseBtn,"click",()=>this.close()),this.options.closeOnMaskClick&&this.addEvent(this.imageContainer,"click",t=>{t.target==this.imageContainer&&this.close()}),this.addEvent(document,"keydown",t=>this.handleKeydown(t));const e=t(()=>{this.handleResize()},300);this.addEvent(window,"resize",e),this.bindDragEvents(),this.bindTouchEvents()}addEvent(t,e,n,i){t.addEventListener(e,n,i);const a=`${e}-${Date.now()}-${Math.random()}`;this.eventListeners.set(a,{element:t,event:e,handler:n})}removeAllEvents(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t.removeEventListener(e,n)}),this.eventListeners.clear()}rotatePoint(t,e,n){const i=n*Math.PI/180,a=Math.cos(i),o=Math.sin(i);return{x:t*a-e*o,y:t*o+e*a}}bindDragEvents(){this.addEvent(this.image,"mousedown",t=>{0===t.button&&(this.isDragging=!0,this.image.classList.add("dragging"),this.startX=t.clientX,this.startY=t.clientY,this.startTranslateX=this.translateX,this.startTranslateY=this.translateY,t.preventDefault())}),this.addEvent(document,"mousemove",t=>{if(!this.isDragging)return;const e=t.clientX-this.startX,n=t.clientY-this.startY,i=this.rotatePoint(e,n,-this.rotation);this.translateX=this.startTranslateX+i.x,this.translateY=this.startTranslateY+i.y,this.updateImageTransform(),t.preventDefault()}),this.addEvent(document,"mouseup",()=>{this.isDragging&&(this.isDragging=!1,this.image.classList.remove("dragging"))}),this.addEvent(document,"mouseleave",()=>{this.isDragging&&(this.isDragging=!1,this.image.classList.remove("dragging"))}),this.addEvent(this.imageContainer,"wheel",t=>{t.preventDefault();const e=this.imageContainer.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top,a=t.deltaY>0?-.05:.05;this.zoomAtPoint(a,n,i)}),this.addEvent(this.image,"dblclick",t=>{t.preventDefault();const e=this.imageContainer.getBoundingClientRect(),n=t.clientX-e.left,i=t.clientY-e.top;if(Math.abs(this.scale-1)<.01)if(this.hasPreviousState)this.scale=this.lastScale,this.translateX=this.lastTranslateX,this.translateY=this.lastTranslateY,this.hasPreviousState=!1;else{this.lastScale=this.scale,this.lastTranslateX=this.translateX,this.lastTranslateY=this.translateY,this.hasPreviousState=!0;const t=1.5,e=t/this.scale,a=this.imageContainer.clientWidth,o=this.imageContainer.clientHeight;this.translateX=this.translateX*e+n-a/2-e*(n-a/2),this.translateY=this.translateY*e+i-o/2-e*(i-o/2),this.scale=t}else{this.lastScale=this.scale,this.lastTranslateX=this.translateX,this.lastTranslateY=this.translateY,this.hasPreviousState=!0;const t=1,e=t/this.scale,a=this.imageContainer.clientWidth,o=this.imageContainer.clientHeight;this.translateX=this.translateX*e+n-a/2-e*(n-a/2),this.translateY=this.translateY*e+i-o/2-e*(i-o/2),this.scale=t}this.updateImageTransform(),this.updateZoomIndicator()})}bindTouchEvents(){this.addEvent(this.image,"touchstart",t=>{if(this.touchState.lastTouchTime=Date.now(),1===t.touches.length)this.touchState.isPinching||(this.touchState.isDragging=!0,this.touchState.startX=t.touches[0].clientX,this.touchState.startY=t.touches[0].clientY,this.touchState.startTranslateX=this.translateX,this.touchState.startTranslateY=this.translateY,this.image.classList.add("dragging"));else if(2===t.touches.length){this.touchState.isPinching=!0,this.touchState.isDragging=!1,this.image.classList.remove("dragging");const e=t.touches[0],n=t.touches[1];this.touchState.initialDistance=this.getDistance(e,n),this.touchState.initialScale=this.scale,this.touchState.initialTranslateX=this.translateX,this.touchState.initialTranslateY=this.translateY,this.touchState.centerX=(e.clientX+n.clientX)/2,this.touchState.centerY=(e.clientY+n.clientY)/2;const i=this.imageContainer.getBoundingClientRect(),a=this.touchState.centerX-i.left,o=this.touchState.centerY-i.top;this.calculateRelativeCenter(a,o),this.touchState.movementCount=0,this.touchState.scaleRatio=1}t.preventDefault()}),this.addEvent(this.image,"touchmove",t=>{if(!(Date.now()-this.touchState.lastTouchTime<16)){if(this.touchState.lastTouchTime=Date.now(),1===t.touches.length&&this.touchState.isDragging&&!this.touchState.isPinching){const e=t.touches[0].clientX-this.touchState.startX,n=t.touches[0].clientY-this.touchState.startY,i=this.rotatePoint(e,n,-this.rotation);this.touchState.movementCount++,(this.touchState.movementCount>this.touchState.stabilizationThreshold||Math.abs(e)>5||Math.abs(n)>5)&&(this.translateX=this.touchState.startTranslateX+i.x,this.translateY=this.touchState.startTranslateY+i.y,this.updateImageTransform())}else if(2===t.touches.length&&this.touchState.isPinching){const e=t.touches[0],n=t.touches[1],i=this.getDistance(e,n);this.touchState.scaleRatio=i/this.touchState.initialDistance;const a=this.touchState.initialScale*this.touchState.scaleRatio,o=.1,s=5,r=Math.max(o,Math.min(s,a));if(Math.abs(r-this.scale)>this.touchState.minScaleChange){const t=r/this.touchState.initialScale,e=this.imageContainer.getBoundingClientRect(),n=e.width,i=e.height;this.translateX=this.touchState.initialTranslateX*t+this.touchState.centerX-n/2-t*(this.touchState.centerX-n/2),this.translateY=this.touchState.initialTranslateY*t+this.touchState.centerY-i/2-t*(this.touchState.centerY-i/2),this.scale=r,this.updateImageTransform(),this.updateZoomIndicator()}}t.preventDefault()}}),this.addEvent(this.image,"touchend",t=>{0===t.touches.length?(this.touchState.isDragging=!1,this.touchState.isPinching=!1,this.image.classList.remove("dragging")):1===t.touches.length&&this.touchState.isPinching&&(this.touchState.isPinching=!1,this.touchState.isDragging=!0,this.touchState.startX=t.touches[0].clientX,this.touchState.startY=t.touches[0].clientY,this.touchState.startTranslateX=this.translateX,this.touchState.startTranslateY=this.translateY,this.image.classList.add("dragging")),t.preventDefault()}),this.addEvent(this.image,"touchcancel",()=>{this.touchState.isDragging=!1,this.touchState.isPinching=!1,this.image.classList.remove("dragging")})}getDistance(t,e){const n=t.clientX-e.clientX,i=t.clientY-e.clientY;return Math.sqrt(n*n+i*i)}calculateRelativeCenter(t,e){if(!this.imageMetadata[this.currentIndex])return;const n=this.imageContainer.clientWidth,i=this.imageContainer.clientHeight/2,a=t-n/2-this.translateX,o=e-i-this.translateY;this.touchState.relativeCenterX=a/this.scale,this.touchState.relativeCenterY=o/this.scale}zoomAtPoint(t,e,n){const i=this.scale,a=5,o=Math.max(.1,Math.min(a,this.scale+t));if(o===this.scale)return;const s=o/i,r=this.imageContainer.clientWidth,h=this.imageContainer.clientHeight;this.translateX=this.translateX*s+e-r/2-s*(e-r/2),this.translateY=this.translateY*s+n-h/2-s*(n-h/2),this.scale=o,this.updateImageTransform(),this.updateZoomIndicator()}zoom(t){const e=this.imageContainer.clientWidth,n=this.imageContainer.clientHeight;this.zoomAtPoint(t,e/2,n/2)}rotate(t){const e=this.rotation,n=e+t;if(e===n)return;const i=this.imageContainer.getBoundingClientRect(),a=i.width/2,o=i.height/2,s=a+this.translateX,r=o+this.translateY;this.rotation=n,this.translateX=0,this.translateY=0;const h=this.imageMetadata[this.currentIndex];if(h){const t=this.calculateBoundingBox(h.width,h.height,n),e=t.width*this.scale,i=t.height*this.scale;this.translateX=(s-a)*(e/(e-this.translateX)),this.translateY=(r-o)*(i/(i-this.translateY))}this.updateImageTransform(),this.updateZoomIndicator()}calculateBoundingBox(t,e,n){const i=n*Math.PI/180,a=Math.abs(Math.cos(i)),o=Math.abs(Math.sin(i));return{width:t*a+e*o,height:t*o+e*a}}resetTransform(){this.rotation=0;const t=this.imageMetadata[this.currentIndex];t&&this.fitImageToScreen(t.width,t.height)}showOriginalSize(){this.rotation=0,this.scale=1,this.translateX=0,this.translateY=0,this.updateImageTransform(),this.updateZoomIndicator()}downloadImage(){const t=this.images[this.currentIndex],e=this.imageMetadata[this.currentIndex],n=new Image;n.crossOrigin="anonymous",n.onload=()=>{try{const t=document.createElement("canvas");t.width=n.width,t.height=n.height,t.getContext("2d").drawImage(n,0,0);const i=t.toDataURL("image/jpeg"),a=document.createElement("a");a.href=i,a.download=e?e.name:"image.jpg",document.body.appendChild(a),a.click(),document.body.removeChild(a)}catch(n){console.error("图片下载失败:",n),this.downloadOriginalImage(t,e)}},n.onerror=()=>{this.downloadOriginalImage(t,e)},n.src=t}downloadOriginalImage(t,e){try{const n=document.createElement("a");n.href=t,n.download=e?e.name:"image.jpg",document.body.appendChild(n),n.click(),document.body.removeChild(n)}catch(t){console.error("原图下载失败:",t),alert("图片下载失败,可能是跨域限制导致的")}}toggleFullscreen(){document.fullscreenElement?document.exitFullscreen&&(document.exitFullscreen(),this.isFullscreen=!1):(this.container.requestFullscreen().catch(t=>{console.error(`全屏请求失败: ${t.message}`)}),this.isFullscreen=!0)}handleKeydown(t){if("block"===this.container.style.display)switch(t.key){case"Escape":this.close(),t.preventDefault();break;case"ArrowLeft":this.prevImage(),t.preventDefault();break;case"ArrowRight":this.nextImage(),t.preventDefault();break;case"+":case"=":this.zoom(.1),t.preventDefault();break;case"-":this.zoom(-.1),t.preventDefault();break;case"0":this.resetTransform(),t.preventDefault();break;case"f":case"F":this.toggleFullscreen(),t.preventDefault();break;case"i":case"I":this.toggleImageInfo(),t.preventDefault()}}handleResize(){const t=this.imageMetadata[this.currentIndex];if(!t)return;const e=this.imageContainer.clientWidth,n=this.imageContainer.clientHeight,i=this.rotation%360;let a=t.width,o=t.height;90!==i&&270!==i||(a=t.height,o=t.width);const s=Math.min(e/a,n/o),r=a*this.scale,h=o*this.scale,l=r>e||h>n;let c=this.scale;c=l?Math.max(.1,s):s>=1?1:s,Math.abs(c-this.scale)>.01&&(this.scale=c,this.translateX=0,this.translateY=0,this.updateImageTransform(),this.updateZoomIndicator())}show(){this.container.style.display="block",setTimeout(()=>{this.container.style.opacity="1"},10)}close(){this.removeAllEvents(),this.container.style.opacity="0",setTimeout(()=>{this.container.style.display="none";const t=document.getElementById("image-viewer-styles");t&&t.remove(),this.container&&this.container.remove()},300)}}return e}()}(e),t(e.exports)});