Categories
Application Development

How to Build a Simple
Full-Screen Slideshow
with Vanilla JavaScript

In this tutorial you’ll learn how to create a responsive full-screen slideshow with plain JavaScript. To build it, we’ll go through several different front-end tricks. As a bonus, we’ll go a step further and customize the cursor’s appearance as we hover over the slideshow.

As usual, to get an initial idea of what we’ll be building, take a look at the related CodePen demo (check out the larger version for a better experience):

Note: This tutorial won’t discuss how to make the slideshow more accessible (e.g. the buttons). We’ll focus on the CSS and JavaScript. 

1. Begin with the Page Markup

To create the slideshow, we’ll need an element with the slideshow class. Within it, we’ll place a list of slides along with the navigation arrows. 

Each slide will contain a background image. Of course, if you like, feel free to add additional content.

By default, the first slide will appear. But we can configure that behavior by attaching the is-active class to the desired slide.

Here’s the required markup:

<div class="slideshow">
  <ul class="slides">
    <li class="slide is-active">
      <div class="cover" style="background-image: url(IMG_SRC);"></div>
    </li>
    <li class="slide">
      <div class="cover" style="background-image: url(IMG_SRC);">
      </div>           
    </li>
    <li class="slide">
      <div class="cover" style="background-image: url(IMG_SRC);">
      </div>           
    </li>
  </ul>
  <div class="arrows">
    <button class="arrow arrow-prev">
      <span></span>
    </button>
    <button class="arrow arrow-next">
      <span></span>
    </button>
  </div>
</div>
<main>...</main>

2. Define the Styles

With the markup ready, we’ll continue with the main styles of our slideshow.

Stacking Slides

By default, all slides will be stacked on top of another. They will all be hidden, apart from the active slide.

To stack the images, we’ll take advantage of CSS Grid. In fact, I’ve already covered this technique and its advantages compared to the traditional positioning in a recent tutorial. 

Here are the associated styles:

.slideshow .slides {
  display: grid;
}
 
.slideshow .slide {
  grid-column: 1;
  grid-row: 1;
  opacity: 0;
  transition: opacity 0.4s;
}
 
.slideshow .slide.is-active {
  opacity: 1;
}

Each div inside a slide will receive the cover class. This will convert its background image into a full-screen background image:

.cover {
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  height: 100vh;
}

Navigation

The navigation buttons will be absolutely positioned inside the slideshow. Each of the buttons should cover half of the slideshow width, and their height should be equal to the slideshow height.

Initially, all buttons will be hidden. But, as we start moving the cursor within the slideshow, the corresponding button will appear depending on the mouse position. 

Each span inside a button will have an arrow (left or right) as a background image. 

By default, the color of these arrows will be black. However, as we move the cursor inside the slideshow, their color should change and create a contrast against the slide images. The trick for creating this effect is to add mix-blend-mode: difference (or exclusion) and filter: invert(1) to the spans.

To learn more about how various blend modes work in CSS (and what alternatives are available to you) take a look at this guide to blend modes by Jonathan Cutrell:

Moving on, the styles for the navigation are below:

.slideshow {
  cursor: none;
}
 
.slideshow .arrows {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
 
.slideshow .arrows .arrow {
  position: relative;
  width: 50%;
  cursor: inherit;
  visibility: hidden;
  overflow: inherit;
}
 
.slideshow .arrows .is-visible {
  visibility: visible;
}
 
.slideshow .arrows span {
  position: absolute;
  width: 72px;
  height: 72px;
  background-size: 72px 72px;
  mix-blend-mode: difference;
  filter: invert(1);
}
 
.slideshow .arrow-prev span {
  background-image: url(slider-prev-arrow.svg);
}
   
.slideshow .arrow-next span {
  background-image: url(slider-next-arrow.svg);
}

3. Add the JavaScript

Let’s now add interactivity to our slideshow!

For this example, the slideshow will come with three default options which we can customize by specifying the associated custom attributes:

Option
Default Value
Description

autoplay
false
Make slideshow autoplay.

autoplay-interval
4000
The interval time between switching slides in milliseconds.

stop-autoplay-on-hover
false
Stop autoplay mode on hover.

Variables

Before digging into these options, as a first step, we’ll define a bunch of variables which we’ll use later:

const slideshow = document.querySelector(".slideshow");
const list = document.querySelector(".slideshow .slides");
const btns = document.querySelectorAll(".slideshow .arrows .arrow");
const prevBtn = document.querySelector(".slideshow .arrow-prev");
const prevBtnChild = document.querySelector(".slideshow .arrow-prev span");
const nextBtn = document.querySelector(".slideshow .arrow-next");
const nextBtnChild = document.querySelector(".slideshow .arrow-next span");
const main = document.querySelector("main");
const autoplayInterval = parseInt(slideshow.dataset.autoplayInterval) || 4000;
const isActive = "is-active";
const isVisible = "is-visible";
let intervalID;

Initializing Things

When all page assets are ready, we’ll call the init function.

window.addEventListener("load", init);

This function will fire four sub-functions:

function init() {
  changeSlide();
  autoPlay();
  stopAutoPlayOnHover();
  customizeArrows();
}

As we’ll see in a moment, each of these functions will accomplish a certain task.

Loop Through Slides

The changeSlide function will be responsible for looping through slides.

Each time a button is clicked, we’ll perform the following actions:

  1. Grab a copy of the currently active slide.
  2. Remove the is-active class from the active slide.
  3. Check to see which button is clicked. If that’s the next button, we’ll add the is-active class to the slide which immediately follows the active slide. If there isn’t such a slide, the first slide will receive this class.
  4. On the other hand, if the previous button is clicked, we’ll add the is-active class to the previous slide of the active slide. If there isn’t such a slide, the last slide will receive this class.

Here’s the signature of this function:

// variables here
 
function changeSlide() {
  for (const btn of btns) {
    btn.addEventListener("click", e => {
      // 1
      const activeSlide = document.querySelector(".slide.is-active");
      // 2
      activeSlide.classList.remove(isActive);
      // 3
      if (e.currentTarget === nextBtn) {
        activeSlide.nextElementSibling
          ? activeSlide.nextElementSibling.classList.add(isActive)
          : list.firstElementChild.classList.add(isActive);
      } else {
        // 4
        activeSlide.previousElementSibling
          ? activeSlide.previousElementSibling.classList.add(isActive)
          : list.lastElementChild.classList.add(isActive);
      }
    });
  }
}

Autoplay

The autoplay function will be responsible for activating the autoplay mode.

By default, we have to manually cycle through the slides.

To activate autoplay, we’ll add the data-autoplay=”true” attribute to the slideshow. 

We can also adjust the interval time between switching slides by defining the data-autoplay-interval=”number” (default is 4000ms) as a second attribute, like this:

<div class="slideshow" data-autoplay="true" data-autoplay-interval="6000">
  ...
</div>

Here’s the signature of this function:

// variables here
 
function stopAutoPlayOnHover() {
  if (
    slideshow.dataset.autoplay === "true" &&
    slideshow.dataset.stopAutoplayOnHover === "true"
  ) {
    slideshow.addEventListener("mouseenter", () => {
      clearInterval(intervalID);
    });
     
    // touch devices
    slideshow.addEventListener("touchstart", () => {
      clearInterval(intervalID);
    });
  }
}

Stop Autoplay

The stopAutoplayOnHover function will be responsible for disabling the autoplay mode when hovering the slideshow.

To enable this functionality, we’ll pass the data-stop-autoplay-on-hover=”true” attribute to the slideshow, like this:

<div class="slideshow" data-stop-autoplay-on-hover="true">
  ...
</div>

Here’s the signature of this function:

// variables here
 
function stopAutoPlayOnHover() {
  if (
    slideshow.dataset.autoplay === "true" &&
    slideshow.dataset.stopAutoplayOnHover === "true"
  ) {
    slideshow.addEventListener("mouseenter", () => {
      clearInterval(intervalID);
    });
     
    // touch devices
    slideshow.addEventListener("touchstart", () => {
      clearInterval(intervalID);
    });
  }
}

Customizing Arrows

The customizeArrows function will be responsible for manipulating the visibility of the navigation buttons and the position of their spans.

As we’ve discussed before, the navigation buttons will initially be hidden. 

As we move the mouse inside the slideshow, only one of them will appear at a time depending on the mouse position. The position of its span element will also depend on the coordinates of the mouse pointer.

As soon as we leave the slideshow, the buttons will become hidden again.

To accomplish these requirements in various screens/devices, we’ll use two mouse events along with two touch events.

Here’s the code that implements this functionality:

// variables here
 
function customizeArrows() {
  slideshow.addEventListener("mousemove", mousemoveHandler);
  slideshow.addEventListener("touchmove", mousemoveHandler);
  slideshow.addEventListener("mouseleave", mouseleaveHandler);
  main.addEventListener("touchstart", mouseleaveHandler);
}
 
function mousemoveHandler(e) {
  const width = this.offsetWidth;
  const xPos = e.pageX;
  const yPos = e.pageY;
  const xPos2 = e.pageX - nextBtn.offsetLeft - nextBtnChild.offsetWidth;
 
  if (xPos > width / 2) {
    prevBtn.classList.remove(isVisible);
    nextBtn.classList.add(isVisible);
 
    nextBtnChild.style.left = `${xPos2}px`;
    nextBtnChild.style.top = `${yPos}px`;
  } else {
    nextBtn.classList.remove(isVisible);
    prevBtn.classList.add(isVisible);
 
    prevBtnChild.style.left = `${xPos}px`;
    prevBtnChild.style.top = `${yPos}px`;
  }
}
 
function mouseleaveHandler() {
  prevBtn.classList.remove(isVisible);
  nextBtn.classList.remove(isVisible);
}

Conclusion

That’s it, folks! In this tutorial, we managed to implement a flexible full-screen slideshow by writing pure JavaScript code. Hopefully, you enjoyed this journey and gained some new knowledge!

Here’s a reminder of what we built:

Next Steps

There are so many things that you can do to enhance the functionality of this slideshow. Here are some thoughts:

  • Add swipe support and keyboard navigation. As a starting point, you can have a look at this tutorial.
  • So far, the slides appear with a fade animation. Build other animation modes like slide and scale.
  • Make the slideshow emit custom events when a new slide comes into view.
  • Make the code more reusable and solid. You might want to use Prototypal inheritance, ES6 classes, or another JavaScript design pattern.
  • Implement a fallback scenario for browsers that don’t support CSS filter effects (e.g. some versions of Microsoft Edge) for the navigation arrows.

As always, thanks a lot for reading!

Read original article in WebDesign on TutsPlus.