Merge pull request #157 from tiengming/patch-1

Update lightbox.js
This commit is contained in:
Meekdai 2024-07-26 20:46:27 +08:00 committed by GitHub
commit f733212c4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

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