How to make cross-fading images with CSS

Zach
December 13th, 2021

In this post we will show you how to use CSS to create a cross-fading image loop, like so:

some cool pics of Norway

You will be able to decide how many images you want, how long you want each image to display, and how long you want the transition between images to last.

We'll start by showing you the code for the demo above, then at the end we'll discuss how you can customize it.

Note: We will be setting this up as a React component but the markup and CSS can easily be extracted and applied anywhere.

Let's get started!

Markup

For building this demo, we will be using 3 images.

Here's what the markup of your component should look like:

export const Demo = () => {
return (
<div className='container'>
<div className='pic' id='pic3' />
<div className='pic' id='pic2' />
<div className='pic' id='pic1' />
</div>
);
};

Notice that the images should be stacked in the reverse order of how you want them to appear. For example, the image you want to appear first should be on the bottom.

CSS

And here is the vanilla CSS that should be applied to the above markup to give it the proper cross-fade effect:

.container {
position: relative;
display: block;
width: 100%;
max-width: 400px;
height: 280px;
margin: 0 auto;
}
.pic {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
-webkit-animation: fade 24s infinite;
animation: fade 24s infinite;
}
#pic1 {
animation-delay: 0s;
background-image: url('https://www.devtwins.com/images/graphics/norway1.jpeg');
}
#pic2 {
background-image: url('https://www.devtwins.com/images/graphics/norway2.jpeg');
animation-delay: 8s;
}
#pic3 {
background-image: url('https://www.devtwins.com/images/graphics/norway3.jpeg');
animation-delay: 16s;
}
@-webkit-keyframes fade {
0% {
opacity: 1;
}
20% {
opacity: 1;
}
34% {
opacity: 0;
}
88% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade {
0% {
opacity: 1;
}
20% {
opacity: 1;
}
34% {
opacity: 0;
}
88% {
opacity: 0;
}
100% {
opacity: 1;
}
}

Creating + Customizing your own

So you've seen the code for the demo above - great!

But chances are you want to take what you see here and use it to create your own custom cross-fading images.

If this is the case, then you're going to need to understand a little bit more about how the demo above was made.

To recreate your own cross-fading images animation, there are a few key steps (and simple formulas) you'll need to follow:

1. Images and timing

First, choose how many images you will be using. This number is n in the formulas below.

Then, for n images, you must define:

a = presentation time for one image

b = duration for cross-fading

Therefore...

animation-duration = t = (a + b) * n

animation-delay = t/n or a + b

If you're not exactly sure how to use any of this yet, don't worry. We will be walking through a full example at the end.

2. Keyframe calculations

No matter how many images you are using, you will always have 5 keyframes in your fade animation.

But the percentage value of each keyframe must change depending on how many images you are using (n), how long you want each image to display for (a), and how long you want each fade transition to last (b).

Luckily, the formulas for each keyframe do not change. And since the first keyframe is always 0%, and the fifth is always 100%, you only need to calculate values for the middle three:

  1. 0%
  2. a/t * 100%
  3. (a + b) / t * 100% = 1/n * 100%
  4. 100% - (b/t * 100%)
  5. 100%

3. Putting it all together

This final step will just be taking the previous 2 steps and walking through a full example with them.

So let's say we have 4 images, and we want each image to display for 3 seconds. And let's also say that we want the transition for each fade to last 2 seconds.

In other words:

  • n = 4
  • a = 3
  • b = 2

With these values we can quickly find out total animation-duration (t) and what our animation-delay should be:

  • t = (a + b) * n = (3 + 2) * 4 = 20 seconds

  • animation-delay = a + b = 3 + 2 = 5 seconds

With all of this figured out, we now know a few things about what our CSS will look like.

Since we know we'll have 4 images, and that our animation duration is 20 seconds, and our animation delay is 5 seconds, we now can do this:

.container {
position: relative;
display: block;
width: 100%;
max-width: 400px;
height: 280px;
margin: 0 auto;
}
.pic {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
-webkit-animation: fade 20s infinite;
animation: fade 20s infinite;
}
#pic1 {
animation-delay: 0s;
background-image: url('1.png');
}
#pic2 {
background-image: url('2.png');
animation-delay: 5s;
}
#pic3 {
background-image: url('3.png');
animation-delay: 10s;
}
#pic4 {
background-image: url('4.png');
animation-delay: 15s;
}

All that's left is to create the actual fade animation, which we can do by calculating the middle three keyframes we'll need using the formulas we mentioned:

  1. 0%
  2. a/t * 100% = 3/20 * 100% = 15%
  3. (a + b) / t * 100% = 1/n * 100% = 1/4 * 100% = 25%
  4. 100% - (b/t * 100%) = 100% - (2/20 * 100%) = 100% - 10% = 90%
  5. 100%

So without the formulas we can see our five keyframe values are:

  1. 0%
  2. 15%
  3. 25%
  4. 90%
  5. 100%

To apply this directly to the fade animation in our CSS would look like this:

@keyframes fade {
0% {
opacity: 1;
}
15% {
opacity: 1;
}
25% {
opacity: 0;
}
90% {
opacity: 0;
}
100% {
opacity: 1;
}
}

Awesome! That's it, we've now created a whole new cross-fading images component based on new parameters.

Altogether the markup and CSS should now look like this:

Markup

export const Demo2 = () => {
return (
<div className='container'>
<div className='pic' id='pic4' />
<div className='pic' id='pic3' />
<div className='pic' id='pic2' />
<div className='pic' id='pic1' />
</div>
);
};

CSS

.container {
position: relative;
display: block;
width: 100%;
max-width: 400px;
height: 280px;
margin: 0 auto;
}
.pic {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
-webkit-animation: fade 20s infinite;
animation: fade 20s infinite;
}
#pic1 {
animation-delay: 0s;
background-image: url('1.png');
}
#pic2 {
background-image: url('2.png');
animation-delay: 5s;
}
#pic3 {
background-image: url('3.png');
animation-delay: 10s;
}
#pic4 {
background-image: url('4.png');
animation-delay: 20s;
}
@-webkit-keyframes fade {
0% {
opacity: 1;
}
15% {
opacity: 1;
}
25% {
opacity: 0;
}
90% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade {
0% {
opacity: 1;
}
15% {
opacity: 1;
}
25% {
opacity: 0;
}
90% {
opacity: 0;
}
100% {
opacity: 1;
}
}

Ending Notes

Congrats for making it this far! I hope this article was helpful to you.

An extra thing to keep in mind would be figuring out how to optimize image loading in your component, ideally in a way that insures that the first image in the stack always loads before the others.

But other than that, I hope you have fun playing around with what you've learned here, and end up making some awesome stuff with it out there on the web.

If you liked this post, consider following me on twitter and/or subscribing to the blog using the form below.

Recommended Posts

How to increase the WPGraphQL query limit

Learn how to increase the WPGraphQL posts limit beyond the default 100. Increase the default WPGraphQL query limit.

Read more

Understanding Mapping vs. Array in Solidity

Learn what the mapping and array types are in Solidity, how they work, and when to use one over the other in a Solidity smart contract. Mapping vs. Array in Solidity explained.

Read more