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:
Conversation
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
asquerySelector
returns the first matching element. This should also resolve a DOM exception that occurs during the switching of slides.It can be a lot of fun revisiting the things that we did in the past with modern tools. Nice work, Jonathan!
var i; // please...
Thanks Marc. A little sloppy of me!
This is great! Oh to be able to ignore IE<=9 ...