diff --git a/plugins/lightbox.js b/plugins/lightbox.js index b7389ef..3fadd76 100644 --- a/plugins/lightbox.js +++ b/plugins/lightbox.js @@ -1,5 +1,4 @@ (function() { - // 灯箱插件 class Lightbox { constructor(options = {}) { this.options = Object.assign({ @@ -13,11 +12,11 @@ this.images = []; this.currentIndex = 0; this.isOpen = false; - this.isZoomed = false; this.zoomLevel = 1; this.touchStartX = 0; this.touchEndX = 0; this.wheelTimer = null; + this.preloadedImages = {}; this.init(); } @@ -37,7 +36,7 @@ left: 0; width: 100%; height: 100%; - background-color: rgba(255, 255, 255, 0.9); + background-color: transparent; backdrop-filter: blur(5px); display: flex; justify-content: center; @@ -45,9 +44,11 @@ opacity: 0; transition: opacity ${this.options.animationDuration}ms ease; pointer-events: none; + z-index: 10000; } .lb-lightbox-overlay.active { pointer-events: auto; + opacity: 1; } .lb-lightbox-content-wrapper { position: relative; @@ -58,103 +59,70 @@ height: 100%; } .lb-lightbox-container { - max-width: 90%; - max-height: 90%; + width: 100%; + height: 100%; position: relative; transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1); + overflow: hidden; + } + .lb-lightbox-image-wrapper { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; } .lb-lightbox-image { max-width: 100%; max-height: 100%; + height: auto; object-fit: contain; - border-radius: 8px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + border-radius: 16px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity ${this.options.animationDuration}ms ease; + opacity: 0; } - .lb-lightbox-nav { + .lb-lightbox-nav, .lb-lightbox-close { position: absolute; - top: 50%; - transform: translateY(-50%); background-color: rgba(255, 255, 255, 0.8); color: #333; border: none; border-radius: 50%; - width: 50px; - height: 50px; - font-size: 24px; - display: flex; - justify-content: center; - align-items: center; cursor: pointer; - transition: all 0.3s ease; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + width: 50px; + height: 50px; + font-size: 30px; + z-index: 2; + transition: transform 0.2s ease; } .lb-lightbox-nav:hover { - background-color: rgba(255, 255, 255, 1); - transform: translateY(-50%) scale(1.1); - } - .lb-lightbox-nav:active { - transform: translateY(-50%) scale(0.9); + transform: scale(1.1); } .lb-lightbox-prev { left: 20px; + top: calc(50% - 25px); } .lb-lightbox-next { right: 20px; + top: calc(50% - 25px); } .lb-lightbox-close { - position: absolute; top: 20px; right: 20px; - background-color: rgba(255, 255, 255, 0.8); - color: #333; - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - font-size: 24px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - } - .lb-lightbox-close:hover { - background-color: rgba(255, 255, 255, 1); - transform: scale(1.1); - } - .lb-lightbox-close:active { - transform: scale(0.9); } @media (max-width: 768px) { - .lb-lightbox-nav { + .lb-lightbox-nav, .lb-lightbox-close { width: 40px; height: 40px; font-size: 20px; } - .lb-lightbox-close { - width: 35px; - height: 35px; - font-size: 20px; - } - } - @media (prefers-color-scheme: dark) { - .lb-lightbox-overlay { - background-color: rgba(0, 0, 0, 0.9); - } - .lb-lightbox-nav, - .lb-lightbox-close { - background-color: rgba(50, 50, 50, 0.8); - color: #fff; - } - .lb-lightbox-nav:hover, - .lb-lightbox-close:hover { - background-color: rgba(70, 70, 70, 1); - } - .lb-lightbox-image { - box-shadow: 0 10px 30px rgba(255, 255, 255, 0.1); - } } `; document.head.appendChild(style); @@ -163,7 +131,6 @@ createLightbox() { this.overlay = document.createElement('div'); this.overlay.className = 'lb-lightbox-overlay'; - this.overlay.style.zIndex = '-1'; this.contentWrapper = document.createElement('div'); this.contentWrapper.className = 'lb-lightbox-content-wrapper'; @@ -171,6 +138,9 @@ this.container = document.createElement('div'); this.container.className = 'lb-lightbox-container'; + this.imageWrapper = document.createElement('div'); + this.imageWrapper.className = 'lb-lightbox-image-wrapper'; + this.image = document.createElement('img'); this.image.className = 'lb-lightbox-image'; @@ -186,15 +156,17 @@ this.closeButton.className = 'lb-lightbox-close'; this.closeButton.innerHTML = '×'; - this.container.appendChild(this.image); + this.imageWrapper.appendChild(this.image); + this.container.appendChild(this.imageWrapper); this.contentWrapper.appendChild(this.container); this.contentWrapper.appendChild(this.prevButton); this.contentWrapper.appendChild(this.nextButton); this.contentWrapper.appendChild(this.closeButton); this.overlay.appendChild(this.contentWrapper); - document.body.appendChild(this.overlay); + + this.closeButton.addEventListener('click', this.close.bind(this)); } bindEvents() { @@ -215,7 +187,7 @@ if (clickedImage && !this.isOpen) { event.preventDefault(); event.stopPropagation(); - this.images = Array.from(document.querySelectorAll('.markdown-body img')); + this.images = Array.from(document.querySelectorAll('.markdown-body img, table img')); this.currentIndex = this.images.indexOf(clickedImage); this.open(); } @@ -224,17 +196,11 @@ handleOverlayClick(event) { if (event.target === this.overlay && this.options.closeOnOverlayClick) { this.close(); - } else if (!event.target.closest('.lb-lightbox-container')) { - const elementBelow = document.elementFromPoint(event.clientX, event.clientY); - if (elementBelow) { - elementBelow.click(); - } } } handleKeyDown(event) { if (!this.isOpen) return; - switch (event.key) { case 'ArrowLeft': this.showPreviousImage(); @@ -250,16 +216,22 @@ handleWheel(event) { event.preventDefault(); - clearTimeout(this.wheelTimer); - this.wheelTimer = setTimeout(() => { - const delta = Math.sign(event.deltaY); - if (delta > 0) { - this.showNextImage(); - } else { - this.showPreviousImage(); - } - }, 50); + if (event.ctrlKey) { + this.zoomLevel += event.deltaY > 0 ? -0.1 : 0.1; + this.zoomLevel = Math.max(1, this.zoomLevel); + this.image.style.transform = `scale(${this.zoomLevel})`; + } else { + clearTimeout(this.wheelTimer); + this.wheelTimer = setTimeout(() => { + const delta = Math.sign(event.deltaY); + if (delta > 0) { + this.showNextImage(); + } else { + this.showPreviousImage(); + } + }, 50); + } } handleTouchStart(event) { @@ -273,20 +245,14 @@ handleTouchEnd() { const difference = this.touchStartX - this.touchEndX; if (Math.abs(difference) > 50) { - if (difference > 0) { - this.showNextImage(); - } else { - this.showPreviousImage(); - } + difference > 0 ? this.showNextImage() : this.showPreviousImage(); } } open() { this.isOpen = true; - this.overlay.style.zIndex = '10000'; this.overlay.classList.add('active'); - this.showImage(); - this.overlay.style.opacity = '1'; + this.showImage(this.images[this.currentIndex].src); document.body.style.overflow = 'hidden'; if (typeof this.options.onOpen === 'function') { this.options.onOpen(); @@ -294,75 +260,97 @@ } close() { - this.isOpen = false; - this.overlay.style.opacity = '0'; - this.overlay.classList.remove('active'); document.body.style.overflow = ''; - setTimeout(() => { - this.image.style.transform = ''; - this.zoomLevel = 1; - this.isZoomed = false; - this.overlay.style.zIndex = '-1'; - }, this.options.animationDuration); + this.overlay.classList.remove('active'); + this.isOpen = false; + this.clearPreloadedImages(); if (typeof this.options.onClose === 'function') { this.options.onClose(); } + this.unbindEvents(); } showPreviousImage() { if (this.currentIndex > 0) { this.currentIndex--; - this.showImage(); + this.showImage(this.images[this.currentIndex].src); + this.resetButtonScale(this.prevButton); } } showNextImage() { if (this.currentIndex < this.images.length - 1) { this.currentIndex++; - this.showImage(); + this.showImage(this.images[this.currentIndex].src); + this.resetButtonScale(this.nextButton); } } - showImage() { - const imgSrc = this.images[this.currentIndex].src; - this.image.style.opacity = '0'; - + resetButtonScale(button) { + button.style.transform = 'scale(1.1)'; + setTimeout(() => { + button.style.transform = 'scale(1)'; + }, 200); + } + + showImage(imgSrc) { const newImage = new Image(); newImage.src = imgSrc; + newImage.onload = () => { + this.image.style.transition = `opacity ${this.options.animationDuration}ms ease`; + this.image.style.transform = 'scale(1)'; this.image.src = imgSrc; this.image.style.opacity = '1'; + + this.preloadImages(); + this.prevButton.style.display = this.currentIndex === 0 ? 'none' : 'block'; + this.nextButton.style.display = this.currentIndex === this.images.length - 1 ? 'none' : 'block'; + }; + + newImage.onerror = () => { + console.error('Failed to load image:', imgSrc); }; + } - this.prevButton.style.display = this.currentIndex > 0 ? '' : 'none'; - this.nextButton.style.display = this.currentIndex < this.images.length - 1 ? '' : 'none'; + preloadImages() { + const preloadNext = this.currentIndex + 1; + const preloadPrev = this.currentIndex - 1; - if (typeof this.options.onNavigate === 'function') { - this.options.onNavigate(this.currentIndex); + if (preloadNext < this.images.length) { + this.preloadedImages[preloadNext] = new Image(); + this.preloadedImages[preloadNext].src = this.images[preloadNext].src; } - this.preloadImages(); + if (preloadPrev >= 0) { + this.preloadedImages[preloadPrev] = new Image(); + this.preloadedImages[preloadPrev].src = this.images[preloadPrev].src; + } } - zoom(factor) { - this.zoomLevel += factor; - this.zoomLevel = Math.max(1, Math.min(this.zoomLevel, 3)); - this.image.style.transform = `scale(${this.zoomLevel})`; - this.isZoomed = this.zoomLevel !== 1; + clearPreloadedImages() { + Object.keys(this.preloadedImages).forEach(key => { + this.preloadedImages[key].src = ''; + }); + this.preloadedImages = {}; } - preloadImages() { - const preloadNext = (this.currentIndex + 1) % this.images.length; - const preloadPrev = (this.currentIndex - 1 + this.images.length) % this.images.length; - new Image().src = this.images[preloadNext].src; - new Image().src = this.images[preloadPrev].src; + unbindEvents() { + document.removeEventListener('click', this.handleImageClick.bind(this), true); + this.overlay.removeEventListener('click', this.handleOverlayClick.bind(this)); + this.prevButton.removeEventListener('click', this.showPreviousImage.bind(this)); + this.nextButton.removeEventListener('click', this.showNextImage.bind(this)); + this.closeButton.removeEventListener('click', this.close.bind(this)); + document.removeEventListener('keydown', this.handleKeyDown.bind(this)); + this.overlay.removeEventListener('wheel', this.handleWheel.bind(this)); + this.overlay.removeEventListener('touchstart', this.handleTouchStart.bind(this)); + this.overlay.removeEventListener('touchmove', this.handleTouchMove.bind(this)); + this.overlay.removeEventListener('touchend', this.handleTouchEnd.bind(this)); } } - // 将 Lightbox 类添加到全局对象 window.Lightbox = Lightbox; - // 自动初始化 document.addEventListener('DOMContentLoaded', () => { new Lightbox(); });