From a2e40e84d46c067565d13b38aa7c4134216383ea Mon Sep 17 00:00:00 2001 From: tiengming <37439881+tiengming@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:09:30 +0800 Subject: [PATCH 1/2] Create lightbox.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 来个灯箱插件,预览看看:https://code.buxiantang.top/post/ji-yu-CloudFlare%20workers-ying-yong-jin-xing-AI-hui-tu.html --- plugins/lightbox.js | 355 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 plugins/lightbox.js diff --git a/plugins/lightbox.js b/plugins/lightbox.js new file mode 100644 index 0000000..cbbb76a --- /dev/null +++ b/plugins/lightbox.js @@ -0,0 +1,355 @@ +(function() { + // 灯箱插件 + class Lightbox { + constructor(options = {}) { + this.options = Object.assign({ + animationDuration: 300, + closeOnOverlayClick: true, + onOpen: null, + onClose: null, + onNavigate: null + }, options); + + this.images = []; + this.currentIndex = 0; + this.isOpen = false; + this.isZoomed = false; + this.zoomLevel = 1; + this.touchStartX = 0; + this.touchEndX = 0; + + this.init(); + } + + init() { + this.createStyles(); + this.createLightbox(); + this.bindEvents(); + } + + createStyles() { + const style = document.createElement('style'); + style.textContent = ` + .lb-lightbox-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(5px); + display: flex; + justify-content: center; + align-items: center; + opacity: 0; + transition: opacity ${this.options.animationDuration}ms ease; + pointer-events: none; + } + .lb-lightbox-overlay.active { + pointer-events: auto; + } + .lb-lightbox-content-wrapper { + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + .lb-lightbox-container { + max-width: 90%; + max-height: 90%; + position: relative; + transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1); + } + .lb-lightbox-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1); + } + .lb-lightbox-nav { + 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); + } + .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); + } + .lb-lightbox-prev { + left: 20px; + } + .lb-lightbox-next { + right: 20px; + } + .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 { + 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); + } + + 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'; + + this.container = document.createElement('div'); + this.container.className = 'lb-lightbox-container'; + + this.image = document.createElement('img'); + this.image.className = 'lb-lightbox-image'; + + this.prevButton = document.createElement('button'); + this.prevButton.className = 'lb-lightbox-nav lb-lightbox-prev'; + this.prevButton.innerHTML = '❮'; + + this.nextButton = document.createElement('button'); + this.nextButton.className = 'lb-lightbox-nav lb-lightbox-next'; + this.nextButton.innerHTML = '❯'; + + this.closeButton = document.createElement('button'); + this.closeButton.className = 'lb-lightbox-close'; + this.closeButton.innerHTML = '×'; + + this.container.appendChild(this.image); + 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); + } + + bindEvents() { + document.addEventListener('click', this.handleImageClick.bind(this), true); + this.overlay.addEventListener('click', this.handleOverlayClick.bind(this)); + this.prevButton.addEventListener('click', this.showPreviousImage.bind(this)); + this.nextButton.addEventListener('click', this.showNextImage.bind(this)); + this.closeButton.addEventListener('click', this.close.bind(this)); + document.addEventListener('keydown', this.handleKeyDown.bind(this)); + this.image.addEventListener('wheel', this.handleWheel.bind(this)); + this.overlay.addEventListener('touchstart', this.handleTouchStart.bind(this)); + this.overlay.addEventListener('touchmove', this.handleTouchMove.bind(this)); + this.overlay.addEventListener('touchend', this.handleTouchEnd.bind(this)); + } + + handleImageClick(event) { + const clickedImage = event.target.closest('img'); + if (clickedImage && !this.isOpen) { + event.preventDefault(); // 阻止默认行为 + event.stopPropagation(); // 阻止事件冒泡 + this.images = Array.from(document.querySelectorAll('.markdown-body img')); + this.currentIndex = this.images.indexOf(clickedImage); + this.open(); + } + } + + 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(); + break; + case 'ArrowRight': + this.showNextImage(); + break; + case 'Escape': + this.close(); + break; + } + } + + handleWheel(event) { + event.preventDefault(); + const delta = Math.sign(event.deltaY); + this.zoom(delta > 0 ? -0.1 : 0.1); + } + + handleTouchStart(event) { + this.touchStartX = event.touches[0].clientX; + } + + handleTouchMove(event) { + this.touchEndX = event.touches[0].clientX; + } + + handleTouchEnd() { + const difference = this.touchStartX - this.touchEndX; + if (Math.abs(difference) > 50) { + if (difference > 0) { + this.showNextImage(); + } else { + this.showPreviousImage(); + } + } + } + + open() { + this.isOpen = true; + this.overlay.style.zIndex = '10000'; + this.overlay.classList.add('active'); + this.showImage(); + this.overlay.style.opacity = '1'; + document.body.style.overflow = 'hidden'; + if (typeof this.options.onOpen === 'function') { + this.options.onOpen(); + } + } + + 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); + if (typeof this.options.onClose === 'function') { + this.options.onClose(); + } + } + + showPreviousImage() { + if (this.currentIndex > 0) { + this.currentIndex--; + this.showImage(); + } + } + + showNextImage() { + if (this.currentIndex < this.images.length - 1) { + this.currentIndex++; + this.showImage(); + } + } + + showImage() { + const imgSrc = this.images[this.currentIndex].src; + this.image.style.opacity = '0'; + setTimeout(() => { + this.image.src = imgSrc; + this.image.style.opacity = '1'; + }, this.options.animationDuration / 2); + + this.prevButton.style.display = this.currentIndex > 0 ? '' : 'none'; + this.nextButton.style.display = this.currentIndex < this.images.length - 1 ? '' : 'none'; + + if (typeof this.options.onNavigate === 'function') { + this.options.onNavigate(this.currentIndex); + } + } + + 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; + } + + 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; + } + } + + // 将 Lightbox 类添加到全局对象 + window.Lightbox = Lightbox; + + // 自动初始化 + document.addEventListener('DOMContentLoaded', () => { + new Lightbox(); + }); +})(); From a2daa78448378f30314feda11ead5def26090b7b Mon Sep 17 00:00:00 2001 From: tiengming <37439881+tiengming@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:24:43 +0800 Subject: [PATCH 2/2] Update lightbox.js --- plugins/lightbox.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/lightbox.js b/plugins/lightbox.js index cbbb76a..b7389ef 100644 --- a/plugins/lightbox.js +++ b/plugins/lightbox.js @@ -17,6 +17,7 @@ this.zoomLevel = 1; this.touchStartX = 0; this.touchEndX = 0; + this.wheelTimer = null; this.init(); } @@ -68,7 +69,7 @@ object-fit: contain; border-radius: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1); + transition: transform ${this.options.animationDuration}ms cubic-bezier(0.25, 0.1, 0.25, 1), opacity ${this.options.animationDuration}ms ease; } .lb-lightbox-nav { position: absolute; @@ -203,7 +204,7 @@ this.nextButton.addEventListener('click', this.showNextImage.bind(this)); this.closeButton.addEventListener('click', this.close.bind(this)); document.addEventListener('keydown', this.handleKeyDown.bind(this)); - this.image.addEventListener('wheel', this.handleWheel.bind(this)); + this.overlay.addEventListener('wheel', this.handleWheel.bind(this)); this.overlay.addEventListener('touchstart', this.handleTouchStart.bind(this)); this.overlay.addEventListener('touchmove', this.handleTouchMove.bind(this)); this.overlay.addEventListener('touchend', this.handleTouchEnd.bind(this)); @@ -212,8 +213,8 @@ handleImageClick(event) { const clickedImage = event.target.closest('img'); if (clickedImage && !this.isOpen) { - event.preventDefault(); // 阻止默认行为 - event.stopPropagation(); // 阻止事件冒泡 + event.preventDefault(); + event.stopPropagation(); this.images = Array.from(document.querySelectorAll('.markdown-body img')); this.currentIndex = this.images.indexOf(clickedImage); this.open(); @@ -249,8 +250,16 @@ handleWheel(event) { event.preventDefault(); - const delta = Math.sign(event.deltaY); - this.zoom(delta > 0 ? -0.1 : 0.1); + clearTimeout(this.wheelTimer); + + this.wheelTimer = setTimeout(() => { + const delta = Math.sign(event.deltaY); + if (delta > 0) { + this.showNextImage(); + } else { + this.showPreviousImage(); + } + }, 50); } handleTouchStart(event) { @@ -317,10 +326,13 @@ showImage() { const imgSrc = this.images[this.currentIndex].src; this.image.style.opacity = '0'; - setTimeout(() => { + + const newImage = new Image(); + newImage.src = imgSrc; + newImage.onload = () => { this.image.src = imgSrc; this.image.style.opacity = '1'; - }, this.options.animationDuration / 2); + }; this.prevButton.style.display = this.currentIndex > 0 ? '' : 'none'; this.nextButton.style.display = this.currentIndex < this.images.length - 1 ? '' : 'none'; @@ -328,6 +340,8 @@ if (typeof this.options.onNavigate === 'function') { this.options.onNavigate(this.currentIndex); } + + this.preloadImages(); } zoom(factor) {