An image lightbox is one of the popular components of a visual interface. It loads full-size images dynamically and displays them in a popup box. A basic interface of a lightbox comes with navigational arrows to slide the next/previous images and a cross button to close the lightbox. This tutorial explains how to create a simple lightbox gallery using HTML, CSS, and JavaScript.
How to Create a Simple JavaScript Lightbox Gallery
1. First of all, load the Normalize CSS and Google Fonts by adding the following CDN links into the head tag of your HTML document.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:400,700'>
2. After that, create a div element with a class name "content"
, and place your images wrapped with an anchor tag with the "data-src-full"
attribute. Place the link of the full-sized image inside the data-src-full that will be loaded in the lightbox.
Similarly, create a div element with a class name "lightbox"
, define its tabindex attribute with -1 value, and define an aria-hidden attribute with true value. Inside the lightbox, create two div elements and two buttons with the following mentioned class names.
<div class="content"> <a href="#" data-src-full="path/to/full-size-image.jpg"> <img src="path/to/thumbnail.jpg"> </a> <a href="#" data-src-full="path/to/full-size-image.jpg"> <img src="path/to/thumbnail.jpg"> </a> </div> <div class="lightbox" tabindex="-1" role="dialog" aria-hidden="true"> <div class="lightbox-loader"></div> <div class="lightbox-content"></div> <button class="lightbox-previous">‹</button> <button class="lightbox-next">›</button> </div>
3. Style the lightbox gallery using the following CSS styles. You can modify the CSS values in order to customize the lightbox.
@charset "UTF-8"; *, *::before, *::after { box-sizing: border-box; } html { font-size: 62.5%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { font-family: "Roboto", sans-serif; font-size: 1.6rem; line-height: 1.5em; } a { color: #647df9; } img { height: auto; max-width: 100%; vertical-align: middle; } .content { max-width: 480px; margin: 2em auto; } @media all and (min-width: 840px) { .content { max-width: 720px; } } .content img { margin: 0.5em; } .lightbox { width: 100%; height: 100%; padding-right: 10px; padding-left: 10px; background: rgba(250, 250, 250, 0.45); position: fixed; top: 0; left: 0; z-index: -1; opacity: 0; visibility: hidden; display: flex; justify-content: center; align-items: center; transition: z-index 0s 0.3s, opacity 0.3s, visibility 0s 0.3s; } @media all and (min-width: 768px) { .lightbox { padding-right: 35px; padding-left: 35px; } } .js-lightbox-loading .lightbox, .js-lightbox-active .lightbox { z-index: 10; opacity: 1; visibility: visible; transition: z-index 0s 0s, opacity 0.3s, visibility 0s 0s; } .lightbox:before { width: 18px; font-size: 3.2rem; line-height: 18px; color: #000; position: absolute; top: 8px; right: 9px; cursor: pointer; content: "×"; } .lightbox .lightbox-content { min-width: 80px; min-height: 80px; background: #fff; opacity: 0; transform: translateY(-80px); transition: opacity 0s, transform 0s ease-out; } .js-lightbox-active .lightbox .lightbox-content { opacity: 1; transform: translateY(0); transition: opacity 0.3s, transform 0.3s ease-out; } .js-lightbox-first .lightbox .lightbox-content { transform: translateX(40px); transition: transform 0.15s linear; } .js-lightbox-last .lightbox .lightbox-content { transform: translateX(-40px); transition: transform 0.15s linear; } .lightbox .lightbox-content img { min-width: 60px; min-height: 60px; max-height: calc(100vh - 90px); -webkit-transition: box-shadow 0.6s ease-out; box-shadow: 1px 1px 4px #ccc; } .lightbox .lightbox-content img:hover { box-shadow: 1px 8px 20px #999; -webkit-transition: box-shadow 0.6s ease-in; } .lightbox .lightbox-previous, .lightbox .lightbox-next { width: 50px; height: 50px; font-size: 5rem; line-height: 0.75; color: #000; padding: 0 0 0.25em; background: rgba(255, 255, 255, 0); border: none; margin-top: -24px; position: absolute; top: 50%; outline: none; } .js-lightbox-loading .lightbox .lightbox-previous, .js-lightbox-loading .lightbox .lightbox-next { display: none; } .lightbox .lightbox-previous { left: 0; } .lightbox .lightbox-next { right: 0; } .lightbox .lightbox-loader { width: 40px; height: 40px; border-radius: 50%; border: 1px solid rgba(200, 200, 200, 0.3); border-top: 1px solid rgba(0, 0, 0, 0.85); margin-top: -20px; margin-left: -20px; display: none; position: absolute; top: 50%; left: 50%; z-index: 1; -webkit-animation: spinner 1s linear infinite; animation: spinner 1s linear infinite; } .js-lightbox-loading .lightbox .lightbox-loader { display: block; } @-webkit-keyframes spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
4. Now, load the ImagesLoaded JS library that helps to load images dynamically inside the lightbox.
<script src='https://imagesloaded.desandro.com/imagesloaded.pkgd.js'></script>
5. Finally, add the following JavaScript function between the <script> and </script> tags before closing the body tag to initialize the lightbox.
console.clear(); var body = document.body, lightbox = document.querySelector('.lightbox'), lightboxContent = lightbox.querySelector('.lightbox-content'), lightboxImages = [], lightboxPrevious = lightbox.querySelector('.lightbox-previous'), lightboxNext = lightbox.querySelector('.lightbox-next'), photoLink = document.querySelectorAll('.content a'); function getTransitionPrefix() { var element = document.createElement('div'), transitions = { 'transition': 'transitionend', 'webkitTransition': 'webkitTransitionEnd', 'msTransition': 'msTransitionEnd', 'mozTransition': 'MoztransitionEnd' }; for (var transition in transitions) { if (element.style[transition] !== undefined) { return transitions[transition]; } } } function showLightbox(source) { body.classList.add('js-lightbox-loading'); body.classList.remove('js-lightbox-active'); body.classList.remove('js-lightbox-first'); body.classList.remove('js-lightbox-last'); lightboxContent.innerHTML = '<img src="' + source + '" />'; imagesLoaded(lightboxContent, function () { body.classList.remove('js-lightbox-loading'); body.classList.add('js-lightbox-active'); }); } function previousLightbox() { var currentIndex = lightboxImages.indexOf(lightboxContent.querySelector('img').src); currentIndex--; if (currentIndex >= 0) { showLightbox(lightboxImages[currentIndex]); } else { body.classList.add('js-lightbox-first'); lightboxContent.addEventListener(getTransitionPrefix(), function() { body.classList.remove('js-lightbox-first'); }); } } function nextLightbox() { var currentIndex = lightboxImages.indexOf(lightboxContent.querySelector('img').src); currentIndex++; if (currentIndex < lightboxImages.length) { showLightbox(lightboxImages[currentIndex]); } else { body.classList.add('js-lightbox-last'); lightboxContent.addEventListener(getTransitionPrefix(), function() { body.classList.remove('js-lightbox-last'); }); } } function hideLightbox() { body.classList.remove('js-lightbox-loading'); body.classList.remove('js-lightbox-active'); lightboxContent.innerHTML = ''; } lightbox.addEventListener('click', function (event) { if ((event.target === this || event.target === this.querySelector('.loader') || event.target === this.querySelector('.close')) && (body.classList.contains('js-lightbox-loading') || body.classList.contains('js-lightbox-active'))) { hideLightbox(); } }); document.addEventListener('keyup', function (event) { if ((event.keyCode === 27 || event.keyCode === 33 || event.keyCode === 34) && (body.classList.contains('js-lightbox-loading') || body.classList.contains('js-lightbox-active'))) { hideLightbox(); } }); document.addEventListener('mousewheel', function (event) { if (body.classList.contains('js-lightbox-loading') || body.classList.contains('js-lightbox-active')) { hideLightbox(); // REMEBER TO CHECK IF IMAGE IS SCROLLED TO TOP OR BOTTOM + SOME EXTRA } }); var startX = 0, startY = 0, endX = 0, endY = 0; document.addEventListener('touchstart', function (event) { if (event.target === lightboxContent || event.target === lightboxContent.querySelector('img')) { startX = event.pageX || event.touches[0].pageX; startY = event.pageY || event.touches[0].pageY; } }); body.addEventListener('touchmove', function (event) { if (event.target === lightboxContent || event.target === lightboxContent.querySelector('img')) { endX = event.pageX || event.touches[0].pageX; endY = event.pageY || event.touches[0].pageY; } }); document.addEventListener('touchend', function (event) { if (event.target === lightboxContent || event.target === lightboxContent.querySelector('img')) { var distanceX = startX - endX; var distanceY = startY - endY; // event.changedTouches[0].pageX if (Math.abs(distanceX) > Math.abs(distanceY)) { if (distanceX > 0) { nextLightbox(); } else { previousLightbox(); } } else { hideLightbox(); } } console.log(event); }); lightboxPrevious.addEventListener('click', function (event) { previousLightbox(); }); document.addEventListener('keyup', function (event) { if (event.keyCode === 37 && (body.classList.contains('js-lightbox-loading') || body.classList.contains('js-lightbox-active'))) { previousLightbox(); } }); lightboxNext.addEventListener('click', function (event) { nextLightbox(); }); document.addEventListener('keyup', function (event) { if (event.keyCode === 39 && (body.classList.contains('js-lightbox-loading') || body.classList.contains('js-lightbox-active'))) { nextLightbox(); } }); for (var pl = 0, pll = photoLink.length; pl < pll; pl++) { lightboxImages.push(photoLink[pl].getAttribute('data-src-full')); photoLink[pl].addEventListener('click', function (event) { event.preventDefault(); showLightbox(this.getAttribute('data-src-full')); }); }
That’s all! hopefully, you have successfully created a simple JavaScript lightbox gallery. If you have any questions or suggestions, feel free to comment below.