Back to blog
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: