Skip to content

Instantly share code, notes, and snippets.

@lam0819
Created June 25, 2025 09:02
Show Gist options
  • Save lam0819/134a59863442cc9cbabb3a9785253ae5 to your computer and use it in GitHub Desktop.
Save lam0819/134a59863442cc9cbabb3a9785253ae5 to your computer and use it in GitHub Desktop.
Simple ImageLightbox
class ImageLightbox {
constructor(options = {}) {
// Configuration
this.config = {
autoMode: false, // Set to true for automatic mode, false for explicit only
selectors: [
'img.lightbox-trigger',
'img[data-lightbox]',
'img.lightbox-enabled'
],
...options
};
this.images = [];
this.currentIndex = 0;
this.lightbox = null;
this.lightboxImg = null;
this.init();
}
init() {
this.createLightboxHTML();
this.addStyles();
this.attachEvents();
this.scanImages();
}
createLightboxHTML() {
this.lightbox = document.createElement('div');
this.lightbox.id = 'js-lightbox';
this.lightbox.innerHTML = `
<div class="lightbox-content">
<button class="close-btn" id="lightbox-close">&times;</button>
<button class="nav-btn prev-btn" id="lightbox-prev">&#8249;</button>
<button class="nav-btn next-btn" id="lightbox-next">&#8250;</button>
<img id="lightbox-img" src="" alt="">
<div class="image-counter" id="lightbox-counter"></div>
</div>
`;
document.body.appendChild(this.lightbox);
this.lightboxImg = document.getElementById('lightbox-img');
}
addStyles() {
const style = document.createElement('style');
style.textContent = `
#js-lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
justify-content: center;
align-items: center;
}
#js-lightbox.active {
display: flex;
opacity: 1;
}
#js-lightbox .lightbox-content {
position: relative;
max-width: 90%;
max-height: 90%;
transform: scale(0.8);
transition: transform 0.3s ease;
}
#js-lightbox.active .lightbox-content {
transform: scale(1);
}
#js-lightbox img {
width: 100%;
height: auto;
max-width: 100%;
max-height: 90vh;
object-fit: contain;
border-radius: 8px;
}
#js-lightbox .close-btn {
position: absolute;
top: -40px;
right: 0;
background: none;
border: none;
color: white;
font-size: 30px;
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
#js-lightbox .close-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
#js-lightbox .nav-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 24px;
cursor: pointer;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s ease;
}
#js-lightbox .nav-btn:hover {
background-color: rgba(255, 255, 255, 0.4);
}
#js-lightbox .prev-btn {
left: -70px;
}
#js-lightbox .next-btn {
right: -70px;
}
#js-lightbox .image-counter {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 14px;
background: rgba(0,0,0,0.5);
padding: 5px 10px;
border-radius: 15px;
}
@media (max-width: 768px) {
#js-lightbox .nav-btn {
width: 40px;
height: 40px;
font-size: 20px;
}
#js-lightbox .prev-btn {
left: 10px;
top: 20px;
transform: none;
}
#js-lightbox .next-btn {
right: 10px;
top: 20px;
transform: none;
}
#js-lightbox .close-btn {
top: 10px;
right: 50%;
transform: translateX(50%);
}
}
img.lightbox-enabled {
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
img.lightbox-enabled:hover {
transform: scale(1.05);
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
}
`;
document.head.appendChild(style);
}
scanImages() {
if (this.config.autoMode) {
// AUTOMATIC MODE: All images get lightbox
const allImages = document.querySelectorAll('img');
this.images = Array.from(allImages);
} else {
// EXPLICIT MODE: Only images with specific classes/attributes
const selector = this.config.selectors.join(', ');
const explicitImages = document.querySelectorAll(selector);
this.images = Array.from(explicitImages);
}
// Add click events and styling to selected images
this.images.forEach((img, index) => {
img.classList.add('lightbox-enabled');
img.addEventListener('click', (e) => {
e.preventDefault();
this.openLightbox(index);
});
});
}
attachEvents() {
document.getElementById('lightbox-close').addEventListener('click', () => this.closeLightbox());
this.lightbox.addEventListener('click', (e) => {
if (e.target === this.lightbox) {
this.closeLightbox();
}
});
document.getElementById('lightbox-prev').addEventListener('click', () => this.prevImage());
document.getElementById('lightbox-next').addEventListener('click', () => this.nextImage());
document.addEventListener('keydown', (e) => {
if (!this.lightbox.classList.contains('active')) return;
switch(e.key) {
case 'Escape':
this.closeLightbox();
break;
case 'ArrowLeft':
this.prevImage();
break;
case 'ArrowRight':
this.nextImage();
break;
}
});
}
openLightbox(index) {
this.currentIndex = index;
this.updateImage();
this.lightbox.style.display = 'flex';
setTimeout(() => {
this.lightbox.classList.add('active');
}, 10);
document.body.style.overflow = 'hidden';
}
closeLightbox() {
this.lightbox.classList.remove('active');
setTimeout(() => {
this.lightbox.style.display = 'none';
document.body.style.overflow = '';
}, 300);
}
updateImage() {
const currentImg = this.images[this.currentIndex];
this.lightboxImg.src = currentImg.src;
this.lightboxImg.alt = currentImg.alt || '';
document.getElementById('lightbox-counter').textContent = `${this.currentIndex + 1} / ${this.images.length}`;
const prevBtn = document.getElementById('lightbox-prev');
const nextBtn = document.getElementById('lightbox-next');
if (this.images.length > 1) {
prevBtn.style.display = 'flex';
nextBtn.style.display = 'flex';
} else {
prevBtn.style.display = 'none';
nextBtn.style.display = 'none';
}
}
prevImage() {
this.currentIndex = this.currentIndex > 0 ? this.currentIndex - 1 : this.images.length - 1;
this.updateImage();
}
nextImage() {
this.currentIndex = this.currentIndex < this.images.length - 1 ? this.currentIndex + 1 : 0;
this.updateImage();
}
addImage(imgElement) {
if (!this.images.includes(imgElement)) {
this.images.push(imgElement);
imgElement.classList.add('lightbox-enabled');
imgElement.addEventListener('click', (e) => {
e.preventDefault();
this.openLightbox(this.images.indexOf(imgElement));
});
}
}
refresh() {
this.images = [];
this.scanImages();
}
// Public method to toggle auto mode
setAutoMode(enabled) {
this.config.autoMode = enabled;
this.refresh();
}
}
// Initialize with configuration
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
// CONFIGURATION - Change autoMode here
window.imageLightbox = new ImageLightbox({
autoMode: false // Set to true for automatic, false for explicit only
});
});
} else {
window.imageLightbox = new ImageLightbox({
autoMode: false // Set to true for automatic, false for explicit only
});
}
<!-- These will have lightbox -->
<img src="image1.jpg" class="lightbox-trigger" alt="Image 1">
<img src="image2.jpg" data-lightbox alt="Image 2">
<img src="image3.jpg" class="lightbox-enabled" alt="Image 3">
<!-- These will NOT have lightbox -->
<img src="logo.jpg" alt="Logo">
<img src="icon.png" alt="Icon">
<script>
// Explicit mode (default)
window.imageLightbox = new ImageLightbox({ autoMode: false });
// Automatic mode
window.imageLightbox = new ImageLightbox({ autoMode: true });
// Switch to automatic mode
window.imageLightbox.setAutoMode(true);
// Switch to explicit mode
window.imageLightbox.setAutoMode(false);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment