Search

Image Cross Fade Transition

Posted on 11th April 2008 — A frequent query and request I receive (and have myself) as a developer is: “how can I fade one image in to another?”.

In particular, Nathan Wrigley of pictureandword.com, needed a method one image into another mouse rolls over, and the slowly fade back once the mouse has left.

Image rollovers were the staple JavaScript nugget of the 90s, and for a lot of JavaScript developers I know, one of the starting places their passion for JavaScript. Today, rollovers are a no-brainer - either in CSS or with the simplest of JavaScript:

$(function () {
  $('img.swap').hover(function () {
    this.src = 'images/sad.jpg';
  }, function () {
    this.src = 'images/happy.jpg';
  });
});

Today’s challenge is the rollover transition!

Watch the complete screencast (alternative flash version)

(QuickTime version is approx. 20Mb, flash version is streaming)

Fade Effect Example

How to Approach the Problem

There are a few different ways which problem can be solved (and I’d love to hear alternative methods via the comments).

Here are the ways I’m going to go through:

  1. Two Image
  2. Single Image
  3. Pure CSS

The key to all of these techniques is how the rendered markup (i.e. what the browser finally sees) is arranged: all of which are very similar.

Essentially, the end image for the transition must sit absolutely in the same position as the starting image.

It’s also worth keeping in mind that the images we fade between should be the same size (height & width-wise).

Note: all three of these techniques have a caveat: styling the start or end image may cause the effect to break. I would recommend wrapping the image in a div or span and styling that element, as it will require less changes to the JavaScript. Either way: it is always best to test in the target browsers.

Two Image Technique

I should start by crediting Karl Swedberg who runs Learning jQuery. He solved Nathan’s transition problem using the following technique.

Karl’s method starts with the two images in the markup: both the start and end images. They are contained in a div and the end image is contained in a further div with absolute positioning.

It is important to note that this technique works best for absolutely position images. Changing the div.fade to position: relative means the div element remains as a block element, and div will stretch the with of it’s container element (defaulting to 100%).

View the working example and the source

HTML

<div class="fade">
  <a href="/info.html"><img src="start.jpg" /></a>
  <div>
    <a href="/info.html"><img src="end.jpg" /></a>
  </div>
</div>

CSS

Obviously if I had more than one fading image, I would use an ID or alternative class to position the top and left CSS properties.

.fade {
  position: absolute;
  top: 100px
  left: 100px
}

.fade div {
  position: absolute;
  top: 0;
  left: 0;
  display: none;
}

jQuery

// when the DOM is ready:
$(document).ready(function () {
  // find the div.fade elements and hook the hover event
  $('div.fade').hover(function() {
    // on hovering over, find the element we want to fade *up*
    var fade = $('> div', this);
    
    // if the element is currently being animated (to a fadeOut)...
    if (fade.is(':animated')) {
      // ...take it's current opacity back up to 1
      fade.stop().fadeTo(250, 1);
    } else {
      // fade in quickly
      fade.fadeIn(250);
    }
  }, function () {
    // on hovering out, fade the element out
    var fade = $('> div', this);
    if (fade.is(':animated')) {
      fade.stop().fadeTo(3000, 0);
    } else {
      // fade away slowly
      fade.fadeOut(3000);
    }
  });
});

Single Image Technique

This takes the two image technique further. I like the idea that we should let the JavaScript add the sugar to the markup - in that we should really only want an image tag, and using some method to know what image we want to fade to.

This technique allows us to insert the image in the markup as we would if there were no transition effect, and the image can be inline, rather being positioned absolutely.

We are going to use the background-image CSS property to specify the target image to fade to.

View the working example and the source

HTML

<img class="fade" 
  src="images/who.jpg" 
  style="background: url(images/who_ro.jpg);" />

CSS

Other than the inline background image - none is required. You can also apply the background-image using classes if you like.

If we wanted to absolutely position the image, or float: right for instance, the best way to do this (if we want to keep the transition), would be to wrap it in a div and style that element.

jQuery

Using jQuery, we execute the following tasks:

  1. Wrap the image in a span
  2. Insert a new image, whose source is the background-image of our start image
  3. Position the new image so that sits directly behind the starting image
  4. Bind hover event to start the effect
// create our transition as a plugin
$.fn.crossfade = function () {
  return this.each(function () { 
    // cache the copy of jQuery(this) - the start image
    var $$ = $(this);
    
    // get the target from the backgroundImage + regexp
    var target = $$.css('backgroundImage').replace(/^url|[()]/g, ''));
    
    // nice long chain: wrap img element in span
    $$.wrap('<span style="position: relative;"></span>')
      // change selector to parent - i.e. newly created span
      .parent()
      // prepend a new image inside the span
      .prepend('<img>')
      // change the selector to the newly created image
      .find(':first-child')
      // set the image to the target
      .attr('src', target);
    
    // position the original image
    $$.css({
      'position' : 'absolute', 
      'left' : 0, 
      // this.offsetTop aligns the image correctly inside the span
      'top' : this.offsetTop
    });
    
    // note: the above CSS change requires different handling for Opera and Safari,
    // see the full plugin for this.
    
    // similar effect as single image technique, except using .animate 
    // which will handle the fading up from the right opacity for us
    $$.hover(function () {
      $$.stop().animate({
          opacity: 0
      }, 250);
    }, function () {
      $$.stop().animate({
          opacity: 1
      }, 3000);
    });
  });
};

// Not only when the DOM is ready, but when the images have finished loading,
// important, but subtle difference to $(document).ready();
$(window).bind('load', function () {
  // run the cross fade plugin against selector
  $('img.fade').crossfade();
});

Pure CSS Technique

If I’m honest, this final technique is a bit cheeky - but still valid. It uses CSS animations only (currently) available in Safari 3 (and WebKit).

However, this is a great example of how to the leverage the CSS using an iPhone (over using JavaScript).

The HTML is the same rendered HTML from the single image technique - but it requires zero JavaScript.

View the working example and the source (to see the effect, view using Safari 3).

HTML

<span style="position: relative;">
    <img src="images/who_ro.jpg" />
    <img 
      style="position: absolute; left: 0px;" 
      src="images/who.jpg" class="fade" />
</span>

CSS

Although this is only supported in Safari 3, the roll over still works in Firefox (and could work in IE7 - though not IE6 because :hover only works on anchors) - because it’s changing the image’s opacity on :hover.

img.fade {
  opacity: 1;
  -webkit-transition: opacity 1s linear;
}

img.fade:hover {
  opacity: 0;
}

Taking it Further

I’ve taken the single image technique further in to a complete plugin. It’s designed to allows us to pass options to control the type of bind, delays, callbacks and tests before running the animation.

Download the full plugin

You can see the plugin in action in this simple memory game I put together quickly.

It pulls the latest photos from flickr, shuffles them, and then sets your memory skills to work.

It’s obviously just a quick prototype - and I’m not sure what happens when you go beyond level 5! Enjoy.

Related posts

Demo

If you find this demo doesn't work as expected, it's possibly due to the demo running from within an iframe. Try running the demo in it's own window.

Source Code

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Fade Method 2 (single image)</title>
    <style type="text/css" media="screen">
    <!--
        BODY { margin: 10px; padding: 0; font: 1em "Trebuchet MS", verdana, arial, sans-serif; font-size: 100%; }
        H1 { margin-bottom: 2px; }
    -->
    </style>

    <script type="text/javascript" src="/js/jquery.js"></script>
    
    <script type="text/javascript">
    <!--

    // wrap as a jQuery plugin and pass jQuery in to our anoymous function
    (function ($) {
        $.fn.cross = function (options) {
            return this.each(function (i) { 
                // cache the copy of jQuery(this) - the start image
                var $$ = $(this);
                
                // get the target from the backgroundImage + regexp
                var target = $$.css('backgroundImage').replace(/^url|[\(\)'"]/g, '');

                // nice long chain: wrap img element in span
                $$.wrap('<span style="position: relative;"></span>')
                    // change selector to parent - i.e. newly created span
                    .parent()
                    // prepend a new image inside the span
                    .prepend('<img>')
                    // change the selector to the newly created image
                    .find(':first-child')
                    // set the image to the target
                    .attr('src', target);

                // the CSS styling of the start image needs to be handled
                // differently for different browsers
                if ($.browser.msie || $.browser.mozilla) {
                    $$.css({
                        'position' : 'absolute', 
                        'left' : 0,
                        'background' : '',
                        'top' : this.offsetTop
                    });
                } else if ($.browser.opera && $.browser.version < 9.5) {
                    // Browser sniffing is bad - however opera < 9.5 has a render bug 
                    // so this is required to get around it we can't apply the 'top' : 0 
                    // separately because Mozilla strips the style set originally somehow...                    
                    $$.css({
                        'position' : 'absolute', 
                        'left' : 0,
                        'background' : '',
                        'top' : "0"
                    });
                } else { // Safari
                    $$.css({
                        'position' : 'absolute', 
                        'left' : 0,
                        'background' : ''
                    });
                }

                // similar effect as single image technique, except using .animate 
                // which will handle the fading up from the right opacity for us
                $$.hover(function () {
                    $$.stop().animate({
                        opacity: 0
                    }, 250);
                }, function () {
                    $$.stop().animate({
                        opacity: 1
                    }, 250);
                });
            });
        };
        
    })(jQuery);
    
    // note that this uses the .bind('load') on the window object, rather than $(document).ready() 
    // because .ready() fires before the images have loaded, but we need to fire *after* because
    // our code relies on the dimensions of the images already in place.
    $(window).bind('load', function () {
        $('img.fade').cross();
    });
    
    //-->
    </script>
</head>
<body>
    <h1>Fade Method 2 - Single Image Technique</h1>
    <p>This technique uses jQuery to modify the markup and to animate to fade transition.</p>
    <p><a href="http://jqueryfordesigners.com/image-cross-fade-transition/">Read the article, and see the screencast this demonstration relates to</a></p>
    <div>
        <img class="fade" src="images/who.jpg" style="background: url(images/who_ro.jpg);" alt="Who we are" />
        <img class="fade" src="images/who.jpg" style="background: url(images/who_ro.jpg);" alt="Who we are" />
        <img class="fade" src="images/who.jpg" style="background: url(images/who_ro.jpg);" alt="Who we are" />
    </div>
    <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
    </script>
    <script type="text/javascript">
    _uacct = "UA-1656750-8";
    urchinTracker();
    </script>
</body>
</html>

Comments

  1. Exodo On 12th October 2009 at 00:10

    This doesn’t seem to work on IE8

  2. Jon On 13th October 2009 at 22:10

    FYI, Brent’s update fixed the double-image problems I was having for IE6, IE7, & IE8 - thanks!

    http://jqueryfordesigners.com/image-cross-fade-transition/comment-page-3/#allcomments

  3. Chickenmask On 27th October 2009 at 06:10

    single image technique has problems in ie6…

  4. Karl Bischoff On 3rd November 2009 at 13:11

    I’ve been using the script for a while and my client brought it to my attention that viewing the slideshow in IE6, IE7 the first slide takes a bit longer to load and thus appearing just before it fades to the second. Sometime it works perfectly, but as soon as you refresh the page there is a delay.

    The website is http://www.janetsimoninc.com. Any help would be greatly appreciated.

    Thanks

    Karl

  5. Lavaper On 8th November 2009 at 01:11

    I have read few of previous solutions to the hover bug that stops working after hovering few times on different swaps, but that solution is meant for Single Image Technique how about if I’m using Two Image Technique, what do I need to change there to fix this hovering bug? (I have to use Single Img Tech cause I’m using jQuery.noConflict which doesn’t seem to work with second technique)

  6. web design newcastle On 12th November 2009 at 18:11

    My friend, this is astonishingly high quality. I can’t believe how much time you invested in this post. Thanks a lot for this, it’s really sorted out my jquery rollover woes.

    As you can guess, I’m going with version 2. I really love Jquery - just can’t get enough of it tbh.

    All the best,

    Clément Yeung Easisell.com

  7. web design newcastle On 13th November 2009 at 15:11

    Brent & Jon, thanks for the linkup! It really saved me some headaches!

  8. Claudiu On 18th November 2009 at 10:11

    Hey, how about opera, the fade / out is not working at all, any way to fix this? And yeah, thanks for this excellent article!

  9. Millan Hermana On 24th November 2009 at 11:11

    Hi! First of all, sorry for my poor English. And now, my question: I don’t know too mucho about using jQuery. I’m trying to use the effect to a rotating banner. The banner has 3 images, and i want to make a fade effect between them. My problem is, that i dont know, how i cant apply the fade effect if i dont want to use it in rollover but in a time, i mean, every 5 seconds, I want to make a transition between the images. Do you have any idea about this? Anyway, thank you for the post. Bye!

  10. h1brd On 25th November 2009 at 21:11

    Is there any fix for making it work when i need to have the fade div as a relative position?

  11. Roberto Sanchez On 23rd December 2009 at 00:12

    Don’t know how many people read this, but… I’ve found a different way of doing this that also works in all the browsers. Basically, you still have the two images, but the immediate parent of the images must be a div instead of a span. The div has a width small enough that the .fade image wraps to the next line, so that it is immediately under the first image. Then, apply a negative margin-top to the .fade image as far as it will go above the first image. You will notice that it will be out from the top of the first image by 10-20 pixels, so you must apply a negative margin-bottom to the first image until the images line up. Here’s a basic example:

    CSS div { width:232px;} .firstImage { margin-bottom:-19px;} .secondImage { margin-top:-239px;} img {width:232px; height:254px;}

    JS $(’.secondImage’).hover(function() { $(’.secondImage’).stop().fadeOut(250); }, function() { $(’.secondImgae’).stop().fadeIn(250); });

    HTML Gah, it stripped the html, but from the CSS you can probably build up the HTML.

    As you can see, the margins will depend on the height of the images, and the width of the parent div will depend on the width of the images, although the width of the div is much more flexible, as it can be as high as 463px before both images will just appear side-by-side instead of one on top of the other.

  12. Luke On 23rd December 2009 at 04:12

    Is there a way to create this so that instead of the Jquery cross fading images, it fades a series of background images on a parent div (images to be specified in an array or something), however instead of the crossfade happening onfocus, can it be done automatically like a photogallery kind of effect.

    I have been trying to create something like this so I can have a background image changing every 10 seconds or so and yet still have content over the top.

    I am looking for a JQuery solution since Flash is out of the question :)

    Thanks!

  13. Matt Peltier On 30th December 2009 at 18:12

    Hey guys,

    It’s a great tutorial, but I can’t seem to get rid of a gray border that appears around items that use this with anchor tags.

  14. George On 10th January 2010 at 12:01

    I cannot make it to work on my Wordpress. I tried everything far I know. I have checked jQuery and it shows me alert message that it works. But rollover the image - no chance. Any advice?

    Thanks

  15. Belinda On 12th January 2010 at 00:01

    I wonder if I can use the above to do something I’m working on…

    I want to be able to click on the image, and have it transition into a new image for a second or two, before loading a new page.

    If anyone has any thoughts or tips, I’d love to hear them :-)

  16. Mike Hall On 22nd January 2010 at 12:01

    Hi I was using this Css here http://jqueryfordesigners.com/image-cross-fade-transition/ using method three. Just refuring to this section of the code :

    -webkit-transition: opacity 1s linear;
    

    how can i speed up the up time of the fade but maintain the fade down time or really just try and make the fade faster than 1s. Ive tried −1s and 0.5s but non seem to affect the fade time.

    Thanks Mike

Leave your own comment
  • http://