lightbox tutorial by dalton walsh

Tutorial

Creating a lightbox with Swiper JS

There are probably a million different tutorials out there to teach you how to make a lightbox to show off images, but here is the way I ended up creating the module for this site. I use Swiper JS throughout my website to handle all of the carousels, and SCSS for my styling. Swiper JS is an amazing library that I encourage anyone to have a play with, it’s very user friendly and easy to get started with.

Below is an example of the module that we will be building.

Example Lightbox

Click to bring up the lightbox
Click to bring up the lightbox
Click to bring up the lightbox
Click to bring up the lightbox

Let’s get started

For the HTML I just have a div with an attribute of lightbox-toggle and then an image inside. You can go ahead and create as many as you need.

<div class="cb-image-grid__image" lightbox-toggle>
  <img src="http://via.placeholder.com/350x350/7176A7/fff?text=image+two" />
</div>

The JS

Firstly we need to grab some DOM elements that will be used later, I store the body tag and all of the images that are inside a container with the attribute lightbox-toggle as variables. In addition, we create a globals object where we can store some variables later on, as well as an initial swiper variable.

The initLightbox variable just checks to see if the lightboxImages variable found any images, then returns a boolean value. We will use the initLightbox variable to conditionally run our code.

let swiper;
window.globals = {};
const body = document.getElementsByTagName('body')[0];
const lightboxImages = document.querySelectorAll("[lightbox-toggle] img");
const initLightbox = (lightboxImages.length > 0);

Next up we create the “skeleton” for the lightbox carousel which Swiper will then bring to life once it initialises. Essentially we are just creating the divs needed for Swiper to work, as well as the “close” icon. We then append each of them into each of the correct places within the DOM.

At the end of the function, we create some global variables to reference the lightbox and the swiper wrapper within.

const createLightboxSkeleton = () => {
  // Create skeleton for lightbox
  const lightbox = document.createElement('div');
  const lightboxContainer = document.createElement('div');
  const lightboxClose = document.createElement('div');
  const swiperContainer = document.createElement('div');
  const swiperWrapper = document.createElement('div');
  const swiperBtnNext = document.createElement('div');
  const swiperBtnPrev = document.createElement('div');
  const swiperPagination = document.createElement('div');

  // Add classes
  lightbox.classList.add('c-lightbox');
  lightboxContainer.classList.add('c-lightbox__container');
  lightboxClose.classList.add('c-lightbox__close');
  lightboxClose.setAttribute('tabindex', '0');
  lightboxClose.innerHTML = 'X';
  swiperContainer.classList.add('swiper-container');
  swiperWrapper.classList.add('swiper-wrapper');
  swiperBtnNext.classList.add('swiper-button-next');
  swiperBtnPrev.classList.add('swiper-button-prev');
  swiperPagination.classList.add('swiper-pagination');

  // Append created divs
  lightboxContainer.appendChild(lightboxClose);
  swiperContainer.appendChild(swiperWrapper);
  swiperContainer.appendChild(swiperBtnNext);
  swiperContainer.appendChild(swiperBtnPrev);
  swiperContainer.appendChild(swiperPagination);
  lightboxContainer.appendChild(swiperContainer);
  lightbox.appendChild(lightboxContainer);
  body.appendChild(lightbox);

  // Set variables to reference the lightbox
  globals.lightboxRef = document.querySelector('.c-lightbox');
  globals.swiperWrapperRef = document.querySelector(
    '.c-lightbox .swiper-wrapper',
  );
};

Next up we use the initLightbox variable to check we have enough images to run, then immediately call the createLightboxSkeleton function.

From now on the code we write will be added within the initLightbox if statement.

if (initLightbox) {
  createLightboxSkeleton();

  // The rest of the code will go here
}

First, you can see we should clear out any HTML from the swiper wrapper that may be leftover from any previous clicks on images, otherwise, it will just create additional images repeatedly.

Then we use the lightboxImages variable we created initially, we are going to use it to loop through each image and apply a click event listener to each image so it can be clicked to open the lightbox.
If you look inside the imageClick function you can see that we are first creating a “clone” of each image from the page to then be used within the lightbox carousel. We do this by looping through the images again and using javascript to create the dom elements we need, similarly to how we have done previously.

Next, we initialise the swiper using some configuration settings, I won’t go into that much detail about using Swiper, you can find out more by going through the docs here. The most important part here though is that we set the initialSlide value to be the index from the loop so that whichever image we click on is the first image shown within the lightbox carousel.

Finally, still inside the imageClick function, we add a class to open the lightbox carousel to the page and add some style to the body to prevent the user from vertically scrolling.

lightboxImages.forEach((el, index) => {
  // Add click function to lightbox images
  el.addEventListener('click', imageClick, false);

  function imageClick() {
    // Clear swiper before trying to add to it
    globals.swiperWrapperRef.innerHTML = '';

    // Loop through images with lightbox data attr
    // Create html for lightbox carousel
    lightboxImages.forEach((img) => {
      // Create clone of current image in loop
      const image = img.cloneNode(true);
      // Create divs
      const slide = document.createElement('div');
      const imageContainer = document.createElement('div');
      // Add classes
      slide.classList.add('swiper-slide');
      imageContainer.classList.add('c-lightbox__image');
      // Append images to the slides, then slides to swiper wrapper
      imageContainer.appendChild(image);
      slide.appendChild(imageContainer);
      globals.swiperWrapperRef.appendChild(slide);
    });

    // Init Swiper
    swiper = new Swiper('.c-lightbox .swiper-container', {
      initialSlide: index,
      loop: true,
      slidesPerView: 1,
      speed: 750,
      spaceBetween: 16,
      watchOverflow: true,
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
      pagination: {
        el: '.swiper-pagination',
        type: 'fraction',
      },
      zoom: true,
      fadeEffect: {
        crossFade: false,
      },
      keyboard: {
        enabled: true,
        onlyInViewport: true,
      },
      mousewheel: {
        sensitivity: 1,
        forceToAxis: true,
        invert: true,
      },
    });

    // Add the class to open the lightbox
    // Add overflow hidden to the body to prevent scrolling
    globals.lightboxRef.classList.add('open');
    body.classList.add('overflowHidden');
  }
});

Next up, still remaining within the initLightbox if statement, we create some more event listeners to allow the user to close out the lightbox by either clicking or using the “esc” key.

// Close lightbox on click
document.addEventListener('click', ({ target }) => {
  if (target.matches('.c-lightbox__close')) {
    destroySwiper(swiper, 250);
    globals.lightboxRef.classList.remove('open');
    body.classList.remove('overflowHidden');
  }
}, false);

// Close lightbox on escape key press
document.addEventListener('keydown', ({ key }) => {
  if (key === 'Escape') {
    destroySwiper(swiper, 250);
    globals.lightboxRef.classList.remove('open');
    body.classList.remove('overflowHidden');
  }
});

Finally, you might have noticed there is a function called destroySwiper within the last two event listeners. This does what it says and kills the lightbox swiper when it is no longer being used with a slight timeout so it gives it enough time to fade out. This function can be added at the top of the JS file under the variables we set initially.

const destroySwiper = (swiper, timeout) => {
  setTimeout(() => {
    swiper.destroy();
  }, timeout);
};

The CSS

I won’t go into detail about the CSS I’ve used here, it should be pretty straightforward. Simply copy and paste into an SCSS file and you’re good to go.

[lightbox-toggle] {
  cursor: zoom-in;

  &:after {
    position: absolute;
    content: url('data:image/svg+xml; utf8, ');
    height: 32px;
    width: 32px;
    bottom: 0;
    right: 0;
    opacity: 0;
    will-change: opacity;
    transition: opacity 0.2s;
  }

  &:hover {
    &:after {
      opacity: 1;
      filter: drop-shadow(2px 4px 6px black);
    }
  }
}
.c-lightbox {
  $c: &;

  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
  pointer-events: none;
  position: fixed;
  opacity: 0;
  width: 100vw;
  height: 100vh;
  z-index: 9000000;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.85);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transition: opacity 0.3s;

  &.open {
    opacity: 1;
    pointer-events: all;
  }

  #{$c}__container {
    width: 100%;
    height: 100%;
  }

  #{$c}__close {
    z-index: 999999;
    position: absolute;
    cursor: pointer;
    top: 1vh;
    right: 1vw;
    font-size: 30px;
    padding: 20px;
    color: white;

    &:hover {
      color: red;
    }
  }

  .swiper-container {
    width: 100%;
    height: 100%;
  }

  .swiper-slide {
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .swiper-pagination {
    color: white;
  }

  .swiper-button-next,
  .swiper-button-prev {
    color: white;

    &:hover {
      color: red;
    }

    &:after {
      font-size: 30px;
    }
  }

  #{$c}__image {
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    height: 75%;
    width: 75%;
    text-align: center;
    cursor: zoom-in;

    img {
      width: auto;
      height: auto;
      max-height: 90vh;
      max-width: 90vw;
    }
  }
}

There you have it, a quick tutorial on how to create a simple modern lightbox using Swiper JS to handle the carousel functionality. Working on personal projects like this and creating tutorials has been an interesting experience for me. I’m so used to just working to tight deadlines that generally don’t leave time for you as a developer to really craft and labour over each part of your code. Just by writing this very tutorial I have updated and changed parts of the code to improve it greatly. It’s like doing a code review for yourself.

To see a working version with all of the code in one place, check out this Codepen here.

To create multiple lightboxes on one page that are independent of each other, please check out my new tutorial.

Previous post:

Next post:

Leave a Reply

Your email address will not be published. Required fields are marked *

  1. John H says:

    Awesome tutorial! Just what I was looking for!

  2. Joost says:

    Hi Dalton!

    Awesome tutorial!! Indeed just what I was looking for. Love the clean code also.
    I’m trying to make it work with the default wordpress gallery. Using swiper as my default slider for my wordpress and love the library. But I can’t get the lightbox to work. I wonder if you could take a peak and see what i’m doing wrong.
    I’ve changed this const to query the wordpress gallery images:

    const lightboxImages = document.querySelectorAll(“.wp-block-image img”);

    The gallery page: https://staging-joostweddepohlgc.kinsta.cloud/client-galler/

    Thanks and greets from Holland

    1. Hi, thanks for the nice comment!
      It looks like you’ve managed to get it working on your link, is that right?

      1. joost says:

        Hi Dalton,

        Yes got it working. Had to make the scss work with wordpress. Awesome work!!

        1. Ah thats great news, glad you got it working!

  3. joost says:

    easier to check out, i’ve tried to recreate it a codepen. but not working:
    https://codepen.io/weddje/pen/oNGgRBR

  4. ncla says:

    Thanks for this. I was using Glightbox before and when I was starting to run into some issues, and I was thinking – Swiper already has all the necessary modules and features to do the same things as lightbox plugins do, and it is modern, well maintained. It just needs initialization and put over the page full width/height. Stumbled upon your article. I need more additions to do though, but this is great starting point and will allow for a lot more flexibility.

    1. No worries, glad the tutorial could be of help to you!
      I love Swiper JS so being able to make my own lightbox with it was really fun.

      Let me know if you have any questions!

  5. Hey Dadlton!
    Very nice Tutorial! Perfectly explained and easy to follow.
    I was just wondering: What would be necessary to make multiple separate Lightboxes on the same Page work with this code?
    Like having a List of Items which each has some thumbnails which can be clicked to open an own Lightbox.

    1. Hey I just created a tutorial that allows you to create multiple lightboxes on one page that are independent of each other – https://daltonwalsh.com/blog/multi-lightbox-with-swiper-js/
      I hope this will help you.

  6. Hank says:

    Hi Dalton. Thanks so much for your gift to us 🙂

    I’m also wondering how to adapt your code to allow multiple lightboxes on the same page.

    1. Hi Hank 👋
      Because of the interest, I’ll be creating a new post that will detail how to make the lightbox work with multiple on the page.
      It’s a great suggestion, thank you.

    2. Hi Hank, I created a tutorial that allows you to create multiple lightboxes on one page that are independent of each other – https://daltonwalsh.com/blog/multi-lightbox-with-swiper-js/
      I hope this will help you.

  7. Joost says:

    Hi Dalton,

    How we show the captions and postcategory title?
    For the captions i’ve tried to pull them in like this:

    const imageCaption = lightboxImages.next(‘figcaption’).text();

    1. Hi Joost, I would need to see more of your code to get an idea of exactly what you are trying to do.
      Are you trying to achieve something like this – https://northernbytesflyball.com/gallery/ Where each image has a caption that it brings to the lightbox on click?

      1. Joost says:

        Hey Dalton!

        yes, that’s what I’m trying to do. But only showing the title in the lightbox, not on the thumbnails.

        This is the code I have:

        jQuery(document).ready(function($) {
        let swiper;
        window.globals = {};
        const body = document.getElementsByTagName("body")[0];
        const lightboxImages = document.querySelectorAll(".wp-block-gallery img");
        const initLightbox = lightboxImages.length > 0;
        const destroySwiper = (swiper, timeout) => {
          setTimeout(() => {
            swiper.destroy();
          }, timeout);
        };
        
        const imageCaption = document.querySelector(".wp-block-image figcaption");
        //const slide = document.querySelectorAll(".c-lightbox__image");
        
        const createLightboxSkeleton = () => {
          // Create skeleton for lightbox
          const lightbox = document.createElement("div");
          const lightboxContainer = document.createElement("div");
          const lightboxClose = document.createElement("div");
          const lightboxLogo = document.createElement("div");
          const swiperContainer = document.createElement("div");
          const swiperWrapper = document.createElement("div");
          const swiperBtnNext = document.createElement("div");
          const swiperBtnPrev = document.createElement("div");
          const swiperPagination = document.createElement("div");
        
          // Add classes
          lightbox.classList.add("c-lightbox");
          lightboxContainer.classList.add("c-lightbox__container");
          lightboxLogo.classList.add("c-lightbox__logo");
          lightboxLogo.innerHTML = "JW";
          lightboxClose.classList.add("c-lightbox__close");
          lightboxClose.setAttribute("tabindex", "0");
          lightboxClose.innerHTML = "";
          swiperContainer.classList.add("swiper");
          swiperWrapper.classList.add("swiper-wrapper");
          swiperBtnNext.classList.add("swiper-button-next");
          swiperBtnPrev.classList.add("swiper-button-prev");
          swiperPagination.classList.add("swiper-pagination");
          imageCaption.classList.add("swiper-title");
        
          // Append created divs
          lightboxContainer.appendChild(lightboxClose);
          lightboxContainer.appendChild(lightboxLogo);
          swiperContainer.appendChild(imageCaption);
          swiperContainer.appendChild(swiperWrapper);
          swiperContainer.appendChild(swiperBtnNext);
          swiperContainer.appendChild(swiperBtnPrev);
          swiperContainer.appendChild(swiperPagination);
          lightboxContainer.appendChild(swiperContainer);
          lightbox.appendChild(lightboxContainer);
          body.appendChild(lightbox);
        
          // Set variables to reference the lightbox
          globals.lightboxRef = document.querySelector(".c-lightbox");
          globals.swiperWrapperRef = document.querySelector(
            ".c-lightbox .swiper-wrapper"
          );
        };
        
        if (initLightbox) {
          createLightboxSkeleton();
        
          // The rest of the code will go here
          lightboxImages.forEach(function (el, index) {
            // Add click function to lightbox images
            el.addEventListener("click", imageClick, false);
        
            function imageClick() {
              // Clear swiper before trying to add to it
              globals.swiperWrapperRef.innerHTML = "";
        
              // Loop through images with lightbox data attr
              // Create html for lightbox carousel
              lightboxImages.forEach(function (img) {
                // Create clone of current image in loop
                const image = img.cloneNode(true);
                // Create divs
                const slide = document.createElement("div");
                const imageContainer = document.createElement("div");
                // Add classes
                slide.classList.add("swiper-slide");
                imageContainer.classList.add("c-lightbox__image");
                // Append images to the slides, then slides to swiper wrapper
                imageContainer.appendChild(image);
                slide.appendChild(imageContainer);
                globals.swiperWrapperRef.appendChild(slide);
              });
        
              // Init Swiper
        swiper = new Swiper(".c-lightbox .swiper", {
                
                effect:'fade',
                longSwipesMs: 0,
                loopPreventsSlide:false,
                longSwipes: true,
                longSwipesRatio: 0,
                threshold: 0,
                slideToClickedSlide:true,
                speed: 400,
                loopedSlides:2,
                initialSlide: index,
                loop: true,
                slidesPerView: 1,
                watchOverflow: true,
                navigation: {
                  nextEl: ".swiper-button-next",
                  prevEl: ".swiper-button-prev"
                },
              pagination: {
                el: ".swiper-pagination",
                type: "fraction",
                renderFraction: function (currentClass, totalClass, ) {
                 return '' +
                      '...' +
                      '';},
                },
                zoom: true,
                fadeEffect: {
                  crossFade: false
                },
                keyboard: {
                  enabled: true,
                  onlyInViewport: true
                },
                mousewheel: {
                  sensitivity: 1,
                  forceToAxis: true,
                  invert: true
                }
              });
        
              // Add the class to open the lightbox
              // Add overflow hidden to the body to prevent scrolling
              globals.lightboxRef.classList.add("open");
              body.classList.add("overflowHidden");
            }
          });
        
          // Close lightbox on click
          document.addEventListener(
            "click",
            ({ target }) => {
              if (target.matches(".c-lightbox__close")) {
                destroySwiper(swiper, 250);
                globals.lightboxRef.classList.remove("open");
                body.classList.remove("overflowHidden");
              }
            },
            false
          );
        
          // Close lightbox on escape key press
          document.addEventListener("keydown", ({ key }) => {
            if (key === "Escape") {
              destroySwiper(swiper, 250);
              globals.lightboxRef.classList.remove("open");
              body.classList.remove("overflowHidden");
            }
          });
        }
        });
        1. Cool,
          So here is the code I went with for that example.

          lightboxImages.forEach((img) => {
            // Create clone of current image in loop
            const image = img.cloneNode(true);
            // Create divs
            const slide = document.createElement("div");
            const imageContainer = document.createElement("div");
            const caption = document.createElement("div");
            const captionDOM = img.closest('.cb-image-grid__image').querySelector(".cb-image-grid__image--caption");
            const captionText = captionDOM && captionDOM.textContent;
            // Add classes
            slide.classList.add("swiper-slide");
            imageContainer.classList.add("c-lightbox__image");
            caption.classList.add("c-lightbox__caption");
            caption.textContent = captionText;
            // Append images to the slides, then slides to swiper wrapper
            imageContainer.appendChild(image);
            slide.appendChild(imageContainer);
            if (captionText) slide.appendChild(caption);
            globals.swiperWrapperRef.appendChild(slide);
          });

          You would just need to adapt it to how your setup is by swapping my “caption” class for “figcaption” and hopefully that should work for you.

          Cheers