Web Style Sheets CSS tips & tricks

See also the index of all tips.

Using animation for automatic slideshows

One of the things you can do with the 'animation' property of CSS is show a series of slides as a slideshow that plays automatically, i.e., it shows one slide for a few seconds, then the next slide for a few seconds, etc.

In the examples below, the slideshow repeats indefinitely. After the last slide the first one is shown again. But showing each slide only once is just as easy.

The slides in my examples are DIV elements with content. Together they are contained in another DIV element with an ID attribute. It is not necessary to use DIV elements. Almost any other element will do, as long as each slide is one element. Here is the slide set I will be using:

<div id=slideset1>
  <div>
    <h1>This is slide 1</h1>
    <p>Slide 1
    <p>It has a <a href="./">link.</a>
  </div>
  <div>
    <h1>This is the second slide</h1>
    <p>Second slide
  </div>
  <div>
    <h1>This is slide number 3</h1>
    <p>Slide number 3
  </div>
</div>

You can give each slide a style (for these example I just made them green and gave them a green border). Although depending on the method used to animate the slides, there may be some restrictions on what styles you can use.

Below are six different methods to make a slideshow. Each uses different CSS properties to hide and show the slides. Here is an overview of the main characteristics of each method:

#1 #2 #3 #4 #5 #6
Main property visibility top margin-top height z-index opacity
Requires a known maximum height? yes yes yes no yes yes
Requires the same height for all slides? no no yes no no no
Requires knowing the number of slides? yes yes yes yes yes yes
Number of animated slides all all 1 all all all
Works in Opera 12? no no yes no no no
Transition effect - move move - - fade
First slide visible before the start? yes no no yes yes no
Allows transparent backgrounds? yes yes yes yes no yes
Repeats without a gap? yes yes no yes yes yes

Method 1 – visibility

One obvious choice for a property to animate is 'visibility': You set the visibility to visible for the slide to show and to hidden for all others. To make sure all slides are shown in the same place, you can use the 'position' property.

Because the slides are positioned with absolute positioning, if you have some content after the slides, you need to know how much space to reserve. In this case, I know that the largest slide is almost 10em high, so I set the slide set container to 10em. The container also needs 'position: relative', so I can position the slides relative to it:

#slideset1 {height: 10em; position: relative}

Each slide (i.e., each child of the slide set container) is initially invisible and is positioned in the top left corner of the container. Each slide has a reference to the animation called ‘autoplay1’, which I will define below. I also set the slideshow to last 12 seconds and repeat an infinite number of times. (You can put 1 for a slide show that does not repeat.)

#slideset1 > * {visibility: hidden; position: absolute;
  top: 0; left: 0; animation: 12s autoplay1 infinite}

The animation consists of changing the value of 'visibility'. At the start of the animation, the value is set to 'visible'. Since I have three slides, a slide should remain visible for one third of the time and invisible for two thirds, so at 33% into the animation I change the value to 'hidden' again. These rules are grouped in an '@keyframes' rule, like this:

@keyframes autoplay1 {
  0% {visibility: visible}
  33.33% {visibility: hidden}
}

But like this, all three slides animate together and become visible at the same time. I need to stagger the times at which each slide's animation starts. The 'animation-delay' property is designed for that:

#slideset1 > *:nth-child(1) {animation-delay: 0s}
#slideset1 > *:nth-child(2) {animation-delay: 4s}
#slideset1 > *:nth-child(3) {animation-delay: 8s}

Here is the result. (For convenience, I added a button to start and pause the animation. That button is explained in ‘Pausing the animation’ below).

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Method 2 – top

In the previous example, the slides were absolutely positioned inside the container and made transparent. One other way to hide them is to position them outside the container and tell the container to hide content outside itself with 'overflow: hidden'.

In fact, if you move the slides in and out of the container, you can also animate that movement, so you see each slide shift in or out: a nice transition affect. That's what I did in the example below.

The rule for the container element is almost the same as before, with the addition of 'overflow: hidden', so any slides positioned outside the container are not shown:

#slideset2 {height: 10em; position: relative;
  overflow: hidden}

Each slide is initially positioned just below the bottom left corner. ('100%' means 100% of the height of the container.) And each slide has an animation called ‘autoplay2’, defined below, to change its position over time:

#slideset2 > * {position: absolute; top: 100%; left: 0;
  animation: 12s autoplay2 infinite ease-in-out}

The 'ease-in-out' determines the acceleration of the movement. It is one of a predefined set of keywords. 'Ease-in-out' means that the movement starts slowly, accelerates, and finally slows down again. Which is what looks best for our purpose.

The animation moves the slide from below the bottom of the container to the top in half a second (4% of 12 seconds). The position remains unchanged until one third into the animation. Then the slide moves up in half a second again until it is completely above the container. It remains there until the animation starts over:

@keyframes autoplay2 {
  0% {top: 100%}
  4% {top: 0%}
  33.33% {top: 0%}
  37.33% {top: -100%}
  100% {top: -100%}
}

As before, each of the three slides has to start the animation at a different time:

#slideset2 > *:nth-child(1) {animation-delay: 0s}
#slideset2 > *:nth-child(2) {animation-delay: 4s}
#slideset2 > *:nth-child(3) {animation-delay: 8s}

And here is the result. Note that the first slide isn't visible before the animation starts.

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Method 3 – margin-top

The third method animates the 'margin-top' property of the first slide to move the slide into view, then move it up to let the second slide appear, and then up even more to let the following slides move into view.

Only one property of one slide needs to be animated. The other slides are laid out below the first in the normal way and move up when the first one moves up. All the slides need to have the same height, which is also the height of the container DIV. That container has 'overflow: hidden' so that the slides that are above or below it remain invisible.

As before, I set the height of the container to 10em and also set the height of each slide to the same value ('100%'). The slides must not have margins; and nothing, including the border or padding, must extend beyond those 10em:

#slideset3 {height: 10em; position: relative;
  overflow: hidden}
#slideset3 > * {height: 100%; box-sizing: border-box;
  overflow: hidden}

The first slide gets an animation that moves it in half a second (4% of 12 seconds) from below the container to the top of the container. About a third into the animation, the slide is moved up another 10em, which causes the second slide to be aligned with the container. A third later, the first slide moves up again. And at the end of the animation it moves a final time, causing the third slide to move out of the top of the container. Then the animation starts over.

#slideset3 > *:first-child {
  animation: 12s autoplay3 infinite ease-in-out}
@keyframes autoplay3 {
  0% {margin-top: 10em}
  4% {margin-top: 0em}
  31% {margin-top: 0em}
  35% {margin-top: -10em}
  64% {margin-top: -10em}
  68% {margin-top: -20em}
  96% {margin-top: -20em}
  100% {margin-top: -30em}
}

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Note that there is a little ‘gap’ in the animation when it starts over. First the slides move out of the top in about half a second and then they move back in from the bottom. So there is a moment when the container is empty of slides. That is unlike the previous method, where the first slide starts moving in when the last slide is still moving out.

Method 4 – height

A way to make an element invisible is also to remove its border and padding and set its height to zero. If its 'overflow' property is also set to 'hidden', the element is completely invisible.

In this case you do not need to know the maximum height of the slides in advance. The animation just toggles the height between 0 and 'auto'.

However, if different slides have different heights, if means that any content after the slides moves along with the slides. You can see that happening in the example below. Whether that is an issue or a feature depends on what comes after the slides…

The following rule gives each slide an animation and sets 'overflow' to 'hidden', so that no contents shows once its height is set to 0.

#slideset4 > * {animation: 12s autoplay4 infinite;
  overflow: hidden}

The animation in this case starts by setting the height to 'auto' and adding the desired padding and border. At one third into the animation, the height is set to 0 and the border and padding are removed in a fraction of a second. They remain zero until the end of the animation.

@keyframes autoplay4 {
  0% {height: auto; padding: 0.5em 1em; border: thin solid}
  33.32% {height: auto; padding: 0.5em 1em; border: thin solid}
  33.33% {height: 0; padding: 0; border: none}
  100% {height: 0; padding: 0; border: none}
}

As in earlier methods, the animations of each slide are staggered in time. However, we do not delay their start in this case, because then all slides would be visible until the animations hide them. Instead, we start the animations of the second and third slides immediately, but partway into the animation. The second slide is started two-thirds into the animation (as if it had already done 8 of the 12 seconds). And the third slide is started one-third into the animation.

#slideset4 > *:nth-child(1) {animation-delay: 0s}
#slideset4 > *:nth-child(2) {animation-delay: -8s}
#slideset4 > *:nth-child(3) {animation-delay: -4s}

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Method 5 – z-index

The fifth method uses the 'z-index' property to put the slides either in front of or behind the container. If the container is opaque, that means the slides that are behind it are invisible.

The container needs to be big enough to hide the biggest of the slides. I know that my slides are no more than 10em in height, so I set the container to that height. It gets a 'position: relative' so that the slides can be positioned inside it and a background to make it opaque. In this case I made the background white, to blend in with the rest of the page.

#slideset5 {height: 10em; position: relative;
  background: white}

I position each slide at the top left in the container, but with a negative 'z-index', to put it behind the container's background.

#slideset5 > * {animation: 12s autoplay5 infinite;
  position: absolute; top: 0; left: 0; z-index: -1}

The animation is simple. It just toggles the 'z-index' between 0 (putting the slide in front of the parent container) and -1 (behind it). For one third of the time, the 'z-index' is 0, until a fraction of a second before the one-third mark, and then it becomes -1, until the animation repeats.

@keyframes autoplay5 {
  0% {z-index: 0}
  33.32% {z-index: 0}
  33.33% {z-index: -1}
}

As before, we start the animations at a different time for each slide:

#slideset5 > *:nth-child(1) {animation-delay: 0s}
#slideset5 > *:nth-child(2) {animation-delay: 4s}
#slideset5 > *:nth-child(3) {animation-delay: 8s}

And here is the result:

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Method 6 – opacity

The sixth method uses the 'opacity' property to make slides invisible. The opacity is a number between 0 (fully transparent) and 1 (fully opaque) and so it can also be used for a transition effect: slides fade in and fade out.

The slides are all positioned in the same place, with absolute positioning, and so I also need to reserve enough space. In this case I know that the biggest is 10em tall. So here is the rule for the container DIV:

#slideset6 {height: 10em; position: relative}

Each slide is positioned in the container and gets an 'opacity' of 0, i.e., it is fully transparent (invisible).

#slideset6 > * {animation: 12s autoplay6 infinite linear;
  position: absolute; top: 0; left: 0; opacity: 0.0}

For this animation I chose a linear progression, instead of the 'ease-in-out' I used in some other methods. For the fading effect I think it looks better.

The animation changes the opacity from 0 to 1 in half a second, and, 3½ seconds later, changes it back to 0, again in half a second (between 33.33% and 37.33%):

@keyframes autoplay6 {
  0% {opacity: 0.0}
  4% {opacity: 1.0}
  33.33% {opacity: 1.0}
  37.33% {opacity: 0.0}
  100% {opacity: 0.0}
}

As before, each slide starts its animation 4 seconds after the preceding slide. That means each slide starts fading in exactly as the previous slide starts fading out. (33.33% of 12 seconds is 4 seconds.)

#slideset6 > *:nth-child(1) {animation-delay: 0s}
#slideset6 > *:nth-child(2) {animation-delay: 4s}
#slideset6 > *:nth-child(3) {animation-delay: 8s}

This is slide 1

Slide 1

It has a link.

This is the second slide

Second slide

This is slide number 3

Slide number 3

Pausing the animation

If something moves in a page, it is always a good idea to provide a way to make it stop. The reader may want more time to look at something before it disappears. Indeed, being able to stop animations is one of W3C's accessibility guidelines.

Stopping a CSS animation is done with a property called 'animation-play-state'. It has two values, 'running' (the default) and 'paused'. The trick is to add a checkbox to the page and set the property to 'paused', only if the user checked that box.

The property has to apply to the slides. So to be able to make a selector that selects a slide based on whether there is a checked checkbox in its context, the checkbox has to come before the slides. The markup with the checkbox added could look like this:

<input type=checkbox name=pause1 id=pause1 checked>
<div id=slideset1>
 <div>
  <h1>This is slide 1</h1>
  <p>Slide 1</p>
  <p>It has a <a href="./">link.</a></p>
 </div>
 <div>
  <h1>This is the second slide</h1>
  <p>Second slide</p>
 </div>
 <div>
  <h1>This is slide number 3</h1>
  <p>Slide number 3</p>
 </div>
</div>

And the CSS rule to stop the animation looks like this:

#pause1:checked ~ #slideset1 > * {
  animation-play-state: paused}

It selects all slides in slideset1 if that slideset follows the pause1 element and that pause1 element is currently checked.

If having a checkbox above the slides is not desirable, you can hide the checkbox and put a LABEL element somewhere else. The LABEL can be put anywhere. It is linked to the checkbox by its FOR attribute.

In the examples above, I put a label after the slides and hid the checkbox, with a rule like this:

#pause1 {display: none}

I also wanted the label to be different depending on whether the animation was running or paused. So I gave the label two different contents (two SPAN elements), only one of which is shown at a time:

<p><label for=pause1><span class=running>⏸
   Pause</span><span class=paused>▶ Play</span></label>

That does, however, restrict a bit where you can put the LABEL, because it has to be possible to write a selector that selects the checkbox and the label. My LABEL element is inside a P that is a sibling of the checkbox, and my rules look like this:

#pause1 ~ * [for=pause1] .paused {display: none}
#pause1 ~ * [for=pause1] .running {display: inline}
#pause1:checked ~ * [for=pause1] .running {display: none}
#pause1:checked ~ #slideset1 > * {animation-play-state: paused}

More

You can also see a rotating dial next to each pause button. I added that because it looks interesting and it shows that an animation is in progress, even if the slide is currently not moving. I will not explain how it works here, but you can study the source of this page.

When you do that, you will also see that most of the CSS rules are enclosed in a block '@supports (animation-delay: 4s) {…}'. That is to hide the rules relating to animations from browsers and other CSS renderers that do not implement animation. The slides will not look good, or may be completely invisible, if some of the CSS properties are applied, but not the animation itself.

By that means I also hid the LABEL with the play/pause button from implementations that do not do animations, because the button serves no purpose in that case.

Bert Bos, style activity lead
Copyright © 1994–2021 W3C® Privacy policy

Created 27 March 2011;
Last updated Wed 06 Jan 2021 05:40:49 AM UTC

Languages

About the translations