Tutorial: Animated Image Carousel for Email

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.

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.


  .carousel a{
    position:absolute;
    top:0px;
    left:0px; 
  }

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.


@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 ...
}



<!--
Fallback content

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!

Leave a Reply