multi swiper lightbox tutorial by dalton walsh

Tutorial

Creating a multi lightbox with Swiper JS

My last lightbox tutorial with Swiper js received a decent amount of attention which was amazing. However, I was repeatedly asked one question which was “if I could create a tutorial with multiple lightboxes on the same page, all independent from each other?”.

Ask and you shall receive! Here you can find a working Codepen with all the code in one place to see a working example.

Tutorial

This tutorial will be very similar to the old one, with a few minor tweaks so let’s get stuck in.

HTML

For the HTML I just put a class of lightbox on the container of the module, so for me, it is on the section tag. Then for all the images that I want to pull into the lightbox, they have a surrounding div that has an attribute of lightbox-toggle and then with the image inside. You can go ahead and create as many as you need.

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

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 grab the images from the currently selected module via the currentLightboxImgs variable. This is done so we only “clone”  the specific images to create the lightbox that we wish to generate. 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(el) {
    const currentLightboxImgs = el.srcElement.closest(".lightbox").querySelectorAll("[lightbox-toggle] img");

    // 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 click 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);
};

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 tutorial on how to have multiple independent lightboxes on the same page using Swiper JS to handle the carousel functionality.

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

If you have any questions, feel free to leave a comment. Thank you!

Previous post:

Next post:

Leave a Reply

Your email address will not be published.

  1. Lene says:

    Hi! Thanks for the tutorial.

    you forgot to finally use the variable:

    currentLightboxImgs.forEach(img => {

    1. Ah, you’re right!
      I need to update the code for this and I just realised the Codepen is working in a slightly off way as well.

      Thanks for the comment!