Skip to content

Instantly share code, notes, and snippets.

@anoduck
Created May 15, 2025 15:42
Show Gist options
  • Save anoduck/b60af6a4d771b951ed1953693a4f65dd to your computer and use it in GitHub Desktop.
Save anoduck/b60af6a4d771b951ed1953693a4f65dd to your computer and use it in GitHub Desktop.
Bootstrap 5 Lightbox Gallery
<svg class="d-none" xmlns="http://www.w3.org/2000/svg">
<symbol id="enlarge" viewBox="0 0 16 16">
<path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1h-4zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zM.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5z"/>
</symbol>
<symbol id="exit" viewBox="0 0 16 16">
<path d="M5.5 0a.5.5 0 0 1 .5.5v4A1.5 1.5 0 0 1 4.5 6h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5zm5 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 10 4.5v-4a.5.5 0 0 1 .5-.5zM0 10.5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 6 11.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zm10 1a1.5 1.5 0 0 1 1.5-1.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4z"/>
</symbol>
</svg>
<h1 class="text-center mb-0">Bootstrap 5 Lightbox Gallery</h1>
<p class="text-center mb-4">Click an image to reveal the lightbox</p>
<section class="photo-gallery">
<div class="container">
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 gallery-grid">
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/251/1200/800.webp">
<img src="https://picsum.photos/id/251/480/320.webp" class="img-fluid" alt="Lorem ipsum dolor sit amet">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/678/1200/800.webp">
<img src="https://picsum.photos/id/678/480/320.webp" class="img-fluid" alt="Ipsum lorem dolor sit amet">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/74/1200/800.webp">
<img src="https://picsum.photos/id/74/480/320.webp" class="img-fluid" alt="Dolor lorem ipsum sit amet">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/92/1200/800.webp">
<img src="https://picsum.photos/id/92/480/320.webp" class="img-fluid" alt="Sit amet lorem ipsum dolor">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/62/1200/800.webp">
<img src="https://picsum.photos/id/62/480/320.webp" class="img-fluid" alt="Aut ipsam deserunt nostrum quo">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/575/1200/800.webp">
<img src="https://picsum.photos/id/575/480/320.webp" class="img-fluid" alt="Nulla ex nihil eligendi tempora">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/110/1200/800.webp">
<img src="https://picsum.photos/id/110/480/320.webp" class="img-fluid" alt="Nemo perspiciatis voluptatum">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/177/1200/800.webp">
<img src="https://picsum.photos/id/177/480/320.webp" class="img-fluid" alt="Accusantium consequuntur modi">
</a>
</div>
<div class="col">
<a class="gallery-item" href="https://picsum.photos/id/197/1200/800.webp">
<img src="https://picsum.photos/id/197/480/320.webp" class="img-fluid" alt="Dolore asperiores reprehenderit">
</a>
</div>
</div>
</div>
</section>
<footer id="copyright" class="mt-5 text-center">
<div class="container">
<div class="row">
<div class="col">
<p class="mb-0">With <b>Bootstrap 5.3.3</b> and <b>Vanilla JS</b></p>
<p class="mb-0">Photos source: <a target="_blank" href="https://picsum.photos">Picsum photos</a></p>
<p class="mb-0">Code made by <a target="_blank" href="https://adorade.ro">Adorade</a>. License <b>MIT</b></p>
</div>
</div>
</div>
</footer>
<div class="modal fade lightbox-modal" id="lightbox-modal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-fullscreen">
<div class="modal-content">
<button type="button" class="btn-fullscreen-enlarge" aria-label="Enlarge fullscreen">
<svg class="bi"><use href="#enlarge"></use></svg>
</button>
<button type="button" class="btn-fullscreen-exit d-none" aria-label="Exit fullscreen">
<svg class="bi"><use href="#exit"></use></svg>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-body">
<div class="lightbox-content">
<!-- JS content here -->
</div>
</div>
</div>
</div>
</div>
const html = document.querySelector('html');
html.setAttribute('data-bs-theme', 'dark');
document.addEventListener('DOMContentLoaded', () => {
// --- Create LightBox
const galleryGrid = document.querySelector(".gallery-grid");
const links = galleryGrid.querySelectorAll("a");
const imgs = galleryGrid.querySelectorAll("img");
const lightboxModal = document.getElementById("lightbox-modal");
const bsModal = new bootstrap.Modal(lightboxModal);
const modalBody = lightboxModal.querySelector(".lightbox-content");
function createCaption (caption) {
return `<div class="carousel-caption d-none d-md-block">
<h4 class="m-0">${caption}</h4>
</div>`;
}
function createIndicators (img) {
let markup = "", i, len;
const countSlides = links.length;
const parentCol = img.closest('.col');
const curIndex = [...parentCol.parentElement.children].indexOf(parentCol);
for (i = 0, len = countSlides; i < len; i++) {
markup += `
<button type="button" data-bs-target="#lightboxCarousel"
data-bs-slide-to="${i}"
${i === curIndex ? 'class="active" aria-current="true"' : ''}
aria-label="Slide ${i + 1}">
</button>`;
}
return markup;
}
function createSlides (img) {
let markup = "";
const currentImgSrc = img.closest('.gallery-item').getAttribute("href");
for (const img of imgs) {
const imgSrc = img.closest('.gallery-item').getAttribute("href");
const imgAlt = img.getAttribute("alt");
markup += `
<div class="carousel-item${currentImgSrc === imgSrc ? " active" : ""}">
<img class="d-block img-fluid w-100" src=${imgSrc} alt="${imgAlt}">
${imgAlt ? createCaption(imgAlt) : ""}
</div>`;
}
return markup;
}
function createCarousel (img) {
const markup = `
<!-- Lightbox Carousel -->
<div id="lightboxCarousel" class="carousel slide carousel-fade" data-bs-ride="true">
<!-- Indicators/dots -->
<div class="carousel-indicators">
${createIndicators(img)}
</div>
<!-- Wrapper for Slides -->
<div class="carousel-inner justify-content-center mx-auto">
${createSlides(img)}
</div>
<!-- Controls/icons -->
<button class="carousel-control-prev" type="button" data-bs-target="#lightboxCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#lightboxCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
`;
modalBody.innerHTML = markup;
}
for (const link of links) {
link.addEventListener("click", function (e) {
e.preventDefault();
const currentImg = link.querySelector("img");
const lightboxCarousel = document.getElementById("lightboxCarousel");
if (lightboxCarousel) {
const parentCol = link.closest('.col');
const index = [...parentCol.parentElement.children].indexOf(parentCol);
const bsCarousel = new bootstrap.Carousel(lightboxCarousel);
bsCarousel.to(index);
} else {
createCarousel(currentImg);
}
bsModal.show();
});
}
// --- Support Fullscreen
const fsEnlarge = document.querySelector(".btn-fullscreen-enlarge");
const fsExit = document.querySelector(".btn-fullscreen-exit");
function enterFS () {
lightboxModal.requestFullscreen().then({}).catch(err => {
alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
});
fsEnlarge.classList.toggle("d-none");
fsExit.classList.toggle("d-none");
}
function exitFS () {
document.exitFullscreen();
fsExit.classList.toggle("d-none");
fsEnlarge.classList.toggle("d-none");
}
fsEnlarge.addEventListener("click", (e) => {
e.preventDefault();
enterFS();
});
fsExit.addEventListener("click", (e) => {
e.preventDefault();
exitFS();
});
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
:root {
--lightbox: rgb(0 0 0 / 0.75);
--carousel-text: #fff;
}
body {
margin: 1.5rem 0 3.5rem;
}
@keyframes zoomin {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.gallery-item {
display: block;
}
.gallery-item img {
box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.15);
transition: box-shadow 0.2s;
}
.gallery-item:hover img {
box-shadow: 0 1rem 1rem rgba(0, 0, 0, 0.35);
}
.lightbox-modal .modal-content {
background-color: var(--lightbox);
}
.lightbox-modal .btn-close {
position: absolute;
top: 1.25rem;
right: 1.25rem;
font-size: 1.25rem;
z-index: 10;
filter: invert(1) grayscale(100);
}
.lightbox-modal .modal-body {
display: flex;
align-items: center;
padding: 0;
}
.lightbox-modal .lightbox-content {
width: 100%;
}
.lightbox-modal .carousel-indicators {
margin-bottom: 0;
}
.lightbox-modal .carousel-indicators [data-bs-target] {
background-color: var(--carousel-text) !important;
}
.lightbox-modal .carousel-inner {
width: 75%;
}
.lightbox-modal .carousel-inner img {
animation: zoomin 10s linear infinite;
}
.lightbox-modal .carousel-item .carousel-caption {
right: 0;
bottom: 0;
left: 0;
padding-bottom: 2rem;
background-color: var(--lightbox);
color: var(--carousel-text) !important;
}
.lightbox-modal .carousel-control-prev,
.lightbox-modal .carousel-control-next {
width: auto;
}
.lightbox-modal .carousel-control-prev {
left: 1.25rem;
}
.lightbox-modal .carousel-control-next {
right: 1.25rem;
}
@media (min-width: 1400px) {
.lightbox-modal .carousel-inner {
max-width: 60%;
}
}
[data-bs-theme = "dark"] .lightbox-modal .carousel-control-next-icon,
[data-bs-theme = "dark"] .lightbox-modal .carousel-control-prev-icon {
filter: none;
}
.btn-fullscreen-enlarge,
.btn-fullscreen-exit {
position: absolute;
top: 1.25rem;
right: 3.5rem;
z-index: 10;
border: 0;
background: transparent;
opacity: .6;
font-size: 1.25rem;
}
.bi {
display: inline-block;
width: 1em;
height: 1em;
vertical-align: -0.035em;
fill: currentcolor;
}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment