Simplest JavaScript Slideshow

Years ago, I put together a tiny jQuery script for doing a simple slideshow. Due to chaining, it was only a handful of lines. It still needed jQuery, though, which was a 93KB dependency (33KB gzipped).

In the six years since then, browsers have improved their DOM APIs. What would it look like if I tried to write this script now without jQuery and used the DOM APIs along with some handy CSS?

The concept is still the same. First, I want to hide all the images except the first one.

  var root = document.querySelector('.fadein');
  var els = root.querySelectorAll(':not(:first-child)');

I grab the root element of the slideshow. This makes the assumption that there is only ever one slideshow, which is what the slideshow assumed, too. After that, I grab all the elements that are not the first child.

Since the NodeList is not an Array, I can’t use Array functions. Instead, I just use a simple for loop. Yes, I can convert the NodeList into an Array but since this is the only time I need to loop through the elements, I’d be wasting time to do so.

  for (i=0; i < els.length; i++) {
    els[i].classList.add('is-hidden');
  }

In the original jQuery script, the fadeIn and fadeOut routines change the opacity by modifying inline styles. This time around, I used CSS transitions to handle this.

    .fadein img { transition:opacity 1s; opacity:1; }
    .fadein img.is-hidden { opacity:0; }

Since jQuery knew when the animation was over, it could easily move to the next part of the script. We’ll have to use the transitionEnd event to know this.

  root.addEventListener('transitionend', function(){
    root.insertBefore(root.querySelector(':first-child.is-hidden'), null);
  });

The event is attached to the root and rely on event bubbling to respond to when the images are done transitioning. The problem is that the event is fired twice: once for the image fading out and another for the image fading in.

I qualified the image to move to the back to only grab the first child that is hidden. Once it’s moved to the back, the second event firing doesn’t have anything to do.

The last thing we need to do is set up the 3 second interval, just like we had before. Again, we use setInterval. All that happens in the interval, though, is the adding and removing of the is-hidden class.

  setInterval(function(){
    root.querySelector(':first-child').classList.add('is-hidden');
    root.querySelector(':nth-child(2)').classList.remove('is-hidden');
  }, 3000);

The entire demo page is 1KB.

The Full Script

The CSS:

.fadein { position:relative; height:332px; width:500px; outline: 1px solid blue; }
.fadein img { position:absolute; left:0; top:0; transition:opacity 1s; opacity:1; }
.fadein img.is-hidden { opacity:0; }

The Javascript:

  var i=0;
  var root = document.querySelector('.fadein');
  var els = root.querySelectorAll(':not(:first-child)');
  for (i=0; i < els.length; i++) {
    els[i].classList.add('is-hidden');
  }
  root.addEventListener('transitionend', function(){
    root.insertBefore(root.querySelector(':first-child.is-hidden'), null);
  });
  setInterval(function(){
    root.querySelector(':first-child').classList.add('is-hidden');
    root.querySelector(':nth-child(2)').classList.remove('is-hidden');
  }, 3000)

For more slideshows:

For more Snook.ca slideshows:

Published January 04, 2016
Categorized as JavaScript
Short URL: https://snook.ca/s/1061

Conversation

5 Comments · RSS feed
Attila Györffy said on January 04, 2016

Nice and clean. I like the fact that more and more people realise that jQuery is not always necessary for simple operations. The arrival of querySelector changes the game significantly.

One small note: I think you may want to change the selector that matches the upcoming slide:

root.querySelector('.is-hidden') instead of using :first-child as querySelector returns the first matching element. This should also resolve a DOM exception that occurs during the switching of slides.

Scott said on January 04, 2016

It can be a lot of fun revisiting the things that we did in the past with modern tools. Nice work, Jonathan!

Marc Brooks said on January 05, 2016

var i; // please...

Jonathan Snook said on January 06, 2016

Thanks Marc. A little sloppy of me!

Toby said on January 28, 2016

This is great! Oh to be able to ignore IE<=9 ...

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.

Want to learn about scaling CSS for large projects?

I'm available for full and half-day workshops on scalable CSS architecture. I can provide on-site training for your team. Interested?
Get in touch.