CSS: Fading slideshow with a touch of JavaScript
Thanks mainly to WordPress, every other website now has some kind of carousel or slideshow on the homepage. That is despite all advice to the contrary.
Regardless of the merits, these features also tend to use bloated JavaScript and jQuery plug-ins adding hundreds of extra kilobytes to the homepage - slowing things down and damaging SEO on the most important page of your website.
Here we present a simple slideshow which by contrast requires less than 1kb of CSS and JavaScript.
If you're already using HTML5 and ES6 you can check out an upgraded version of the script taking advantage of new browser features.
Setting up the HTML
The starting point is to set up a list of images on the page, wrapped in a DIV. Something like the following:
<div id="stage">
<a href="1.jpg"><img src="1.jpg" width="360" height="270"></a>
<a href="2.jpg"><img src="2.jpg" width="360" height="270"></a>
<a href="3.jpg"><img src="3.jpg" width="360" height="270"></a>
<a href="4.jpg"><img src="4.jpg" width="360" height="270"></a>
<a href="5.jpg"><img src="5.jpg" width="360" height="270"></a>
<a href="6.jpg"><img src="6.jpg" width="360" height="270"></a>
<a href="7.jpg"><img src="7.jpg" width="360" height="270"></a>
<a href="8.jpg"><img src="8.jpg" width="360" height="270"></a>
</div>
In this example all the images have links, but that's not necessary. If you remove the links, you'll just need to change some of the CSS and JavaScript to reference 'img' instead of 'a'. Later we'll look at placing text over the images for a caption, call to action (CTA) or similar.
An improvement would be to have only two images on the page load time, and then post-load and insert the others into the DOM as required. That saves having to load them all at once, making the page faster and boosting SEO.
If we stop here, our images are just going to wrap across the page. What we need is some styles to prevent that from happening.
CSS to stack the images
Here is the CSS we are using for the demonstration below:
<style>
#stage {
margin: 1em auto;
width: 382px;
height: 292px;
}
#stage a {
position: absolute;
}
#stage a img {
padding: 10px;
border: 1px solid #ccc;
background: #fff;
}
#stage a:nth-of-type(1) {
animation-name: fader;
animation-delay: 4s;
animation-duration: 1s;
z-index: 20;
}
#stage a:nth-of-type(2) {
z-index: 10;
}
#stage a:nth-of-type(n+3) {
display: none;
}
@keyframes fader {
from { opacity: 1.0; }
to { opacity: 0.0; }
}
</style>
By setting the links to position: absolute we're taking all the images out of the document flow and stacking them on top of one another. We then need to assign a width and height to #stage to reserve space on the page for the slideshow. This equals the image dimensions plus padding (10px on each side) and borders (1px on each side).
We then use some nth-of-type selectors to place the first image on top of the stack, the second image just behind, and the rest hidden from display.
Finally, we assign an animation keyframe to the top image telling it to wait four seconds before fading out to opacity: 0. All that's missing now is a touch of JavaScript to move the faced image to the bottom of stack so the next image can be displayed and fade out in turn.
A touch of JavaScript
All that is needed here is to assign an event handler to our images that is triggered when the keyframe animation ends. It takes the foremost photo, and moves it to the back. Much as you would do with a pack of playing cards:
<script>
window.addEventListener("DOMContentLoaded", function(e) {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
var stage = document.getElementById("stage");
var fadeComplete = function(e) { stage.appendChild(arr[0]); };
var arr = stage.getElementsByTagName("a");
for(var i=0; i < arr.length; i++) {
arr[i].addEventListener("animationend", fadeComplete, false);
}
}, false);
</script>
The new uppermost image now assumes the nth-of-type(1) properties, including the fader keyframe, and so on through the other images.
And that's it! No bloated code, no plugins, no libraries, just a few lines of vanilla JavaScript which works in all modern browsers.
To support older browsers, there are browser prefixes for WebKit, Mozilla and Microsoft and you can find details in our earlier article on constructing an Infinite Animated Photo Wheel.
Working demonstration
Putting it all together you get a simple fading slideshow:
If you want the slideshow to move slower, or faster, or use a different transition, it's just a matter of adjusting the Transition Timing Function (or animation-timing-function in this case). The borders can also be easily re-styled or removed.
Displaying text over the slideshow
There are many ways to go about this, but perhaps the simplest is to add some title attributes to our links and have them displayed over the image using CSS:
#stage a::after {
position: absolute;
left: 11px;
bottom: 11px;
padding: 2px 0;
width: calc(100% - 22px);
background: rgba(0,0,0,0.5);
text-align: center;
content: attr(title);
font-size: 1.1em;
color: #fff;
}
With no other changes aside from adding titles to our links, we now have the startings of a basic CTA slideshow, which could even be turned into a carousel with a few tweaks to the keyframe.
So if you absolutely have to have a homepage slideshow to please the powers that be, think about ditching jQuery for something like the above. And let us know if you have any feedback or questions using the button below.
Converting to a carousel
A few simple changes to the CSS makes our slideshow behave more like a carousel:
CSS:
<style>
#stage {
margin: 1em auto;
width: 360px;
height: 270px;
border: 10px solid #000;
overflow: hidden;
}
#stage a {
position: relative;
display: inline-block;
}
#stage a::after {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 100%;
text-align: center;
content: attr(title);
font-weight: bold;
font-size: 2em;
color: #fff;
text-shadow: -1px -1px 0 #333, 1px -1px 0 #333, -1px 1px 0 #333, 2px 2px 0 #333;
}
#stage a:nth-of-type(2) {
left: 360px;
top: -50%;
animation-name: slider;
animation-delay: 4s;
animation-duration: 1s;
animation-timing-function: cubic-bezier(0,1.5,0.5,1);
}
#stage a:nth-of-type(n+3) {
display: none;
}
@keyframes slider {
from { transform: translateY(-50%) rotate(30deg); left: 360px; }
to { transform: translateY(-50%); left: 0px; }
}
</style>
JavaScript:
<script>
window.addEventListener("DOMContentLoaded", function(e) {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
var stage = document.getElementById("stage");
var slideComplete = function(e) { stage.appendChild(arr[0]); };
var arr = stage.getElementsByTagName("a");
for(var i=0; i < arr.length; i++) {
arr[i].addEventListener("animationend", slideComplete, false);
}
}, false);
</script>
And of course your first question is going to be how to add navigation spots or arrows over the image...
Working with different size images
If your images differ in size or shape you will see parts of the larger ones protruding from behind the smaller ones. One way around this is to use JavaScript to adjust the padding to make every slide the same dimension:
<script>
window.addEventListener("DOMContentLoaded", function(e) {
// Original JavaScript code by Chirp Internet: www.chirpinternet.eu
// Please acknowledge use of this code by including this header.
var maxW = 0;
var maxH = 0;
var stage = document.getElementById("stage");
var fadeComplete = function(e) { stage.appendChild(arr[0]); };
var arr = stage.getElementsByTagName("img");
for(var i=0; i < arr.length; i++) {
if(arr[i].width > maxW) maxW = arr[i].width;
if(arr[i].height > maxH) maxH = arr[i].height;
}
for(var i=0; i < arr.length; i++) {
if(arr[i].width < maxW) {
arr[i].style.paddingLeft = 10 + (maxW - arr[i].width)/2 + "px";
arr[i].style.paddingRight = 10 + (maxW - arr[i].width)/2 + "px";
}
if(arr[i].height < maxH) {
arr[i].style.paddingTop = 10 + (maxH - arr[i].height)/2 + "px";
arr[i].style.paddingBottom = 10 + (maxH - arr[i].height)/2 + "px";
}
arr[i].addEventListener("animationend", fadeComplete, false);
}
}, false);
</script>
Please note that the above code is for the case where the slideshow contains only images without links around them. If there are links, replace img with a and arr[i] with arr[i].firstChild throughout, with the exception of the last line.
References
- MDN: :nth-of-type
- MDN: Event reference
Related Articles - Transforms and Transitions
- CSS Transition Timing Functions
- CSS Animation Using CSS Transforms
- CSS Bouncing Ball Animation
- CSS 3D Transforms and Animations
- CSS Upgraded fading slideshow
- CSS Animated Background Gradients
- CSS An actual 3D bar chart
- CSS Infinite Animated Photo Wheel
- CSS Photo Rotator with 3D Effects
- JavaScript CSS Animated Fireworks
- JavaScript Animating objects over a curved path
- CSS Fading slideshow with a touch of JavaScript
- JavaScript Controlling CSS Animations
John 17 August, 2022
Would anyone have any input on letting this loop through a set amount of times then stopping it? I've tried a few things but can't seem to get anything to work. Essentially the effect I am looking for is for the user to see the loop once, preferably full-screen(but that may be for another discussion), then for the whole slideshow to disappear and leaves only the webpage content. Thanks in advance for any help.
Chris Sandiford 4 August, 2022
It's a neat little slider but after the first cycle my first image always pops about 10 pixels down and then the whole slideshow is aligned at that point from then on.
Something not right after that first cycle.
Using it on a Sharepoint site so that might not help
Joby 6 April, 2022
Can this be modified to support multiple slideshows on one page please?
Given that ID's have to be unique, I tried to change 'ID' to 'Class', but to no avail. Would there be a specific js edit? Any advice gratefully received. Other than this, the code is simple to implement and clearly explained–thank you so much for sharing.
Here's one we prepared earlier:
www.the-art-of-web.com/css/fading-slideshow-upgraded/
Steven Taylor 14 August, 2021
Yes, I added a background to the figure element, added a border, andadjusted the margins and padding and now it seems to be working great! This is really slick. The one I was using previously had a lot more javascript and it work okay in Chrome on Linux, Chrome on ios, and Safari, but wouldn't work on Chrome on Windows. Rathe than trying to figure that out, this solution is way better. Thanks again.
Steven Taylor 13 August, 2021
I spoke too soon, there is still a problem with the figcaptions where there are sometimes two showing up at the same time. They are a child of the figure element and I would think they should take on the Z-index and opacity from the parent figure element, but maybe there is some going on there that I do not yet understand. Thanks.
I think you just need to add a background to your <figure> or <figcaption> elements so that the caption from the next slide is not visible through the transparent background of the first one.
David Douglas 15 June, 2021
You were right. I had modified the CSS to the point where it didn't work right. I started over copying the CSS exactly as is, replaced "a" with "img" as before, and it works perfectly! This is exactly what I needed. Thank you!
David Douglas 14 June, 2021
Using your "CSS: Fading slideshow with a touch of JavaScript", the top image fades but the following image jerks or pops into view.
How do I get the following image to fade in as the previous image fades out as shown in your demonstration?
In our examples the 'top' image transitions from opacity 1.0 to opacity 0.0 allowing the 'next' image to appear. If you've copied the code correctly, that should work for your slideshow.
In order to have the 'next' image fade in as the 'top' image fades out, you would assign extra CSS something like the following:
#stage a:nth-of-type(2) {
animation-name: fader;
animation-delay: 4s;
animation-duration: 1s;
animation-direction: reverse;
}
The issue is that you can't exactly synchronise separate animations using only @keyframes animations as they can end up starting at slightly different times.
RickH 18 March, 2021
Nice tutorial, and great code!
Was wondering, like others that commented, how to get the image to fit inside a div's container. Probably the div's height, and then centered in the width.
Been playing with it a bit, but can't quite get the CSS tweaked correctly.
Thanks. Great site - other tutorials have been very helpful
Dan Theman 29 September, 2020
Great image transitions - love it. I want to make the images displayed always fit 100% of the browser window width - how do I do that?
Reese 7 September, 2020
Thank you very much for this! I was wondering if there is a way to shuffle the images randomly?
Jackie 18 July, 2020
Hi. I'm trying to work out how to change the animation delay/duration between different images. Basically I want to stay much longer on the final image before going back to repeat. Can that be done?
Using the code as presented all photos are assigned the same @keyframes animation once they come to the front of the pile (nth-of-type(1)).
To treat a particular image differently, you could assign a class and use CSS to alter the animation parameters:
#stage a.longer:nth-of-type(1) {
animation-duration: 4s;
}
Agostinho Domingos 5 June, 2020
Hi guys,
I got this banner rotation to work on my website fine. However, as I try to center the rotational banners on my leftnavbar of my website, it kind of goes crazy to the center of the page cover other wording.
How can I sort this issue out?
Appreciate it very much. Thanks.
Niltac S 14 April, 2020
The slideshow looks great, just what I need, but the html content below is now hidden underneath it. Any ideas why that is happening? Thanks
Rajan Borkhataria 7 February, 2020
Great slide show how do i change the position of the slide show i have tried to use float or give it left, right, bottom and top sizes but nothing is working in the CSS. the slideshow just stays i the middle of the page
do you know what i need to change in CSS
thanks
What we have on the page works out to:
#stage {
position: static;
margin: 1em auto 1em auto;
}
which centres the container on the page.
Changing an 'auto' to '0' will align it left or right, and "position: absolute;" will let you put it anywhere.
Konabob 15 January, 2020
Fantastic!
If I wanted to have the pictures cycle only once, would it be an easy thing to impliment?
apestaartje 29 November, 2019
Hi, very interesting site for carousel or slideshow. But is it possible to make the website work from an image folder, instead of naming the images seperatly in html. In know ik can work with php, but can it work locally, so you only have to put the images in the folder, without further coding?
Thank you for your reply.
JavaScript can't access web server directories directly, but you could create a server-side script for that purpose - converting the file list into JSON format and making it accessible via a public URL.
Rysik 24 January, 2019
Your slider is very cool, the only thing is not clear how to add slide switching points
Barton Lewis 17 December, 2018
I assume you have to have images that have the same exact dimensions. What if you have images with different heights but the same width - or vice versa? Can you have a slideshow in this case where, for example if you keep the width constant but have different heights in some cases, the image centers itself vertically in the slideshow?
The problem is in the way the fade-out works. Both images have to be visible at the same time, with the top one then fading out. When the queued image is larger, it pokes out from behind the current image.
You could try a CSS solution to fore them to the same height:
e.g.
#stage img {
max-height: 800px;
width: auto;
}
But even then you'd want them to be the same shape.
Otherwise you can set larger borders (or margin) on the smaller images to make them cover the same area, and the image centered...
Elias 24 February, 2018
Thank you so mutch, i now have a greate website. Do you hate W3schools, i do. This is so mutch easyer! Tanks you again! �™�