Click to Sign Up for a 7 Day Free Trial!

Email Development

Tutorial: Animated Image Carousel for Email

Email On Acid

This is the first part of a multi-part tutorial series on how to create an animated image carousel that works in email clients that support CSS animation. This article will cover how to build a basic carousel that will fade from one image to the next.

Since CSS animations mostly only work in Webkit based clients, the carousel will be active in iOS Mail (iPhone, iPad), Apple Mail and Outlook for iOS and Mac. Other clients will display the fallback content.

Subsequent articles will add effects such as pan and zooming and sliding. So stay tuned. If you're new to CSS animations, check out this helpful primer by Alex Ilhan.

Three Image Carousel

The carousel we'll be building will contain three images, although you should be able to customize it for as many images as you like.


View completed demo (Use Chrome or Safari to see the animation)

The basics of this carousel are pretty straightforward; we start with a div containing a set of images with links.

<div class="carousel" style="position:relative;width:500px;height:320px;">
  <a href="https://www.google.com/search?q=castles"><img src="http://freshinbox.com/examples/animated-carousel/images/car-castle.jpg" border="0"></a>
  <a href="https://www.google.com/search?q=meadows"><img src="http://freshinbox.com/examples/animated-carousel/images/car-meadow.jpg" border="0"></a>
  <a href="https://www.google.com/search?q=coast"><img src="http://freshinbox.com/examples/animated-carousel/images/car-coast.jpg" border="0"></a>
</div>

Then we're going position the images so they overlap each other using absolute positioning. This will display the last image on top instead of the first. We'll change that in a minute.

<style>
  .carousel a{
    position:absolute;
    top:0px;
    left:0px; 
  }
</style>

Swapping the Images Using z-index

We'll now use the following CSS to cycle through the images in the carousel. Specifically, we'll shift their z-index positions up and down depending on which part of the animation the image is in.

  .carousel a {
    position:absolute;
    top:0px;
    left:0px; 
    -webkit-animation: car-anim 9s linear infinite;    
  }

  .carousel a:nth-child(1){
    -webkit-animation-delay: 0s;    
  }
  .carousel a:nth-child(2){
    -webkit-animation-delay: 3s;    
  }
  .carousel a:nth-child(3){
    -webkit-animation-delay: 6s;    
  }

  @-webkit-keyframes car-anim 
  {
      0% {
        z-index:2;
      }
      33%{
        z-index:2;
      }
      33.001%{
        z-index:1;
      }
      100%{
        z-index:1;
      }
  }

See an example here

As you can see, we have an animation sequence called car-anim. All three images will be sharing the same animation, but they'll start at different times using the animation-delay property. The animation will last a total of 9 seconds (3 seconds for each image).

So far, pretty straightforward stuff.

Fixing Swapping Glitch

If you run the code above, you'll note that at times there's what appears like a very brief glitch when the images swap. This is due to the fact that when cycling the images, the lower level images share the same z-index (1). In absolute position stacked elements, if two elements share the same z-index the bottom-most element appears on top -- therefore there's a brief moment where the 3rd image appears before the 2nd image gets its z-index changed to 2.

This issue doesn't present itself if we cycle the images according to their natural stacking order, (from the 3rd image to the 1st), but I felt conceptually cycling the 3rd image first feels odd, so we fix it by hiding an image using opacity:0 when it is no longer at the top. I add a slight delay when hiding to account for the iOS transition gap.

  @-webkit-keyframes car-anim
  {
      0% {
        z-index:2;
        opacity:1;
      }
      33%{
        z-index:2;
      }
      33.1%{
        z-index:1;
        opacity:1;
      }
      35%{
        opacity:0;
      }
      100%{
        z-index:1;
        opacity:0;
      }
  }

Using Negative Animation Delay to Address iOS Scroll Quirk

In iOS9 the email client pauses animations when the user scrolls the email. However, it does not pause the animation delay timer. This can potentially cause timing issues if the user scrolls the email after the first animation has started but the animations for other images have yet to start. This issue is not present in iOS10.

One solution is to not use animation delay, but that would be a huge setback. Thankfully, there's another option, and that is to use negative animation delays. Negative animation delays as stated in the referenced article "start the animation immediately, as if that amount of time has already gone by." This works for us because any scrolling of the email will pause all animations since they are already running so the animations don't lose sync.

We change our animation delays for our images to the following. You can shift the delays up or down to start the animation at any time but they must be spaced equally apart based on the total animation runtime.

  .carousel a:nth-child(1){
    -webkit-animation-delay: -9s;    
  }
  .carousel a:nth-child(2){
    -webkit-animation-delay: -6s;    
  }
  .carousel a:nth-child(3){
    -webkit-animation-delay: -3s;    
  }

Fade Effect

To improve the carousel experience we'll add a fade effect so that the images fade out and fade in during the image transition. This is achieved using the opacity style. For our purpose we'll set the transition period to 5%. You can set it to a larger or smaller value to speed up or shorten the transition time.

@-webkit-keyframes car-anim
  {
      /* start fade in */
      0%{
        z-index:2;
        opacity:0;
      }
      /* end fade in */
      5%{
        opacity:1;
      }
      33%{
        z-index:2;
      }
      /* lower z-index - allow next image to fade in */
      33.1%{
        z-index:1;
      }
      /* already obscured */
      38%{
        opacity:1;
      }
      /* hide */
      38.1%{
        opacity:0;
      }
      100%{
        z-index:1;
        opacity:0;
      }
  }

The code above will start the animation as the first frame is fading in (the last frame fading out). Since we don’t want to start the animation in the middle of a transition, we push out the animation delay by one second:

  .carousel a:nth-child(1){
    -webkit-animation-delay: -10s;    
  }
  .carousel a:nth-child(2){
    -webkit-animation-delay: -7s;    
  }
  .carousel a:nth-child(3){
    -webkit-animation-delay: -4s;    
  }

Add Responsiveness

To make the carousel scale to the width of its container, we add the following CSS.

  .carousel{
    width:100% !important;
    height:auto !important;
  }

  .carousel a{
    width:100%;
    display:block;
  }  

  .carousel img{
    display:block!important;
    width:100% !important;
    height:auto !important;
  }

We also need to make at least one image position:relative so that the fluid container doesn't collapse now that height is set to auto.

  .carousel a:nth-child(1){
    position:relative;
  }

Fallbacks and Customization

I'll cover a fallback technique for clients that can't show the carousel, as well as some details about how to customize this example for your needs.

Handling Clients Without CSS Animation Support

At this point, our "best case scenario" animated carousel is done. Now we need to ensure that clients that don't support animation don't get a broken experience. There are multiple ways to handle "fallback content" and this article goes into the various options.

The strategy I'll use for this example is to hide the carousel and display a separate "fallback" block for clients that don't support CSS animation. To do this, we display the fallback content by default and then in a -webkit-min-device-pixel-ratio media query we hide the fallback content and display the carousel. This is because Webkit based email clients support CSS animation.

Here's how it looks.

<style>
@media screen and (-webkit-min-device-pixel-ratio: 0) {
    .fallback{
    display:none;
  }
  .carousel{
    display:block !important;
    max-height:none !important;
    position:relative;
  }
  … other CSS animation code ...
}
</style>
<!--[if !mso]><!-- -->
<div class="carousel" style="overflow:hidden;display:none;max-height:0px;">
   Carousel content
</div>
<!--<![endif]-->
<div class="fallback">
   Fallback content
</div>

We also need to hide the carousel from the Zimbra email client (Comcast) and Samsung email client which, although responsive to the -webkit-min-device-pixel-ratio media query, lack the ability to fully render the carousel.

  #MessageViewBody .fallback,
  body.MsgBody .fallback{ 
    display:block; 
  }
  #MessageViewBody .carousel,
  body.MsgBody .carousel{
    display:none !important;
  }

More Images?

If you'd like to have more than 3 images in your carousel, simply add more images into the HTML and adjust your animation blocks appropriately. For four images, instead of fading out at 33%, you fade out at 25% and for five images, you fade out at 20%.

Background Image Approach

There's an amazingly simple approach to swap images in a carousel by changing the URL of background images. Unfortunately, this technique has a side effect in iOS9 that causes the images to glow and fade even when the images are not transitioning. If you're interested, you can see the code for it here. Apparently iOS10 does not suffer from this bug so this may be an option soon.

Finished Code

So there you have it, a fully working responsive animated image carousel!

Check out the completed code. (Use Chrome or Safari to see the animation)

Test with Email on Acid

In the coming weeks there will be more articles that build upon this example, so stay tuned!

When attempting complex email techniques like this one, it's best to test your code and make sure it's looking great everywhere. Try email testing free for a week and see what you think!

About the Author

Justin Khoo

Justin Khoo

Justin Khoo (@freshinbox) is an email developer and writes about email techniques and new innovations. He has been involved in many aspects of email over the past decade, everything from building webmail clients and email campaign services to coding HTML emails.

Join the Discussion

Hi, which clients does this work on? And why is this better than a simple animated gif slideshow?
Paeon
Hi Paeon,

As stated, it works in most webkit browsers and in the default Apple Mail client. The advantage is that GIFs can be very heavy in terms of image size, where as 2 images animated using CSS should use considerably less space. Also, you can add individual links to each image whereas when it's a GIF you can only link the asset once.

I've just built this from the tutorial, it's been a great help and works as expected!

So I hope I answered your question Paeon and Thank you Justin Khoo for writing this post!

Thanks,
G

http://www.meowplates.co.uk
Gordon
Hi!,

It is possible to make a carrousel with another kind of transition, like slide on the X and Y axis.

Thanks!
Diego
@Paeon you can also implement smooth transitions using CSS and animations offer better color fidelity (animated gif has a 256 color limit)

@Gordon thank you for your comments and I'm glad you've put it to the test!

@Diego keep a look out for a follow up article wink
Justin Khoo
Strange, I only see the animation on mobile. What am I doing wrong? Thanks!
hill2001
Ah my media tag is prevented the animation in a browser > @media screen and (max-width: 480px) {

Any ideas how to integrate the 480px max-width into the animation script?
hill2001
Got it! Thanks again for this fantastic script!
hill2001
I'm trying to add more images to the carousel, but I can't seem to get the delay and the length of the animation to match correctly. I have 6 images in total, with delays from -19s til -4s for the last. The length of the animation is 18s now, as I figured your longest delay was -10s and the length of the animation was 9s. But it's either showing the last images too long before finally showing the first and looping through the images or if I shorten the animation it shows the last image for a second before changing to the first, but then it seems to skip 2 and 3.

How do I calculate the length of the animation needed to fix all 6 images in the animation?

Thanks!
Marjolein
Hi @Marjolein

Sorry it has taken me a while to get back to you, but I've posted an example with 6 images here:

https://codepen.io/freshinbox/pen/QvNqoo
Justin Khoo
Hi .. great example !! I'm trying to do only 2 images (fadein) .. so I removed the third image, but now I get a blanc space after the 2nd image. What must I alter so with 2 images it will transition back to image 1?
Fransjo
Hi there,

Trying to get the desktop version to work... with no results.
I got this in my CSS: @media screen and (max-width: 480px) {
Any chance you can help?

Thanks
Amily
Amily C

Leave a Comment