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 screencasts

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. Moksha On 29th October 2008 at 21:10

    thanks nice, i hope it work fine with all brow

  2. Dan On 2nd November 2008 at 11:11

    Works fine in firefox and safari.. not working in IE6 or IE7

  3. Haris On 23rd November 2008 at 08:11

    First, thanks for the script.

    Second, any idea why sometimes it would show both images underneath each other in IE6? Like on my current design, it shows last image twice in IE6. And I think it shows double images for all in IE7. It’s really weird…

    http://www.hbportfolio.net/clients/thestlnightlife/

    Thanks in advance

  4. Ant On 27th November 2008 at 16:11

    Hello, I’m a noob at jQuery and I’m having problems with this.

    I’ve created an online example: http://www.ants-designs.com/jquery-test/

    The files are zipped here: http://www.ants-designs.com/jquery-test/jquery-test.zip

    The problem occurs after hovering over the links or images a number of times in quick succession; some of them tend to hang on the rollover image or sometimes they get stuck completely and I need to refresh the page to reset them. Does anyone have any idea?

    Thanks, Ant

  5. Patrick On 27th December 2008 at 06:12

    Hi, this is an awesome effect, thanks! I would like to find a way to modify the ‘Single Image Technique’ script to add an active or ‘focus’ state, so that when an image is clicked on, it maintains the ‘faded in’ image - but then would of course fade out once another images is selected. Have a feeling this would be easy to implement, but seeing as though am new to jQuery (& JavaScript for that matter) am having trouble. Can anyone help?

  6. Neonic On 27th December 2008 at 09:12

    I have the same problem like Ant does…Thanks for any help.

  7. Ferex On 6th January 2009 at 00:01

    Not working in ie7 if you have many links in a line, the last link falls and the hover stays a top.

    http://www.downhill.tyresbolivia.com

    any idea to solve the issue?

  8. Ferex On 6th January 2009 at 01:01

    Solved

    if ($.browser.msie) { $$.css({ ‘position’ : ‘absolute’, ‘left’ : 0, ‘background’ : ”, ‘top’:0 }); }if ($.browser.mozilla) { $$.css({ ‘position’ : ‘absolute’, ‘left’ : 0, ‘background’ : ”, ‘top’:this.offsetTop });

    Works for me

  9. chris On 9th January 2009 at 19:01

    Anyone have any work-arounds for IE 8??

  10. Steve Crofts On 16th January 2009 at 20:01

    I have had a look at you tutorial looks good, could you tell me if it could be modified so when you click a thumbnail on the left of a page a large image fades in on the right?? Dont know Jquery but will try to learn it soon so will be checking back for your beginners stuff.

    Cheers

    Steve

  11. Eric On 17th January 2009 at 23:01

    i’m having the same issue DJAI did above. how do i remove the border that automatically appears around the background image (example 2), when the image is used as a link? thanks!

  12. Alex On 23rd January 2009 at 23:01

    how would you have a navbar with more than 1 image?

    Would you have to repeat the script for each image, referring each time to a different ID?

    Doesnt sound very efficient so I think there’s something I dunno… can you shed some light please? Thx!

  13. John On 25th January 2009 at 08:01

    I’ve had some luck with the border issue. Don’t know if it’ll work for you guys, but give it a shot!

    Look for the line that says:

    .prepend('<img />')

    and replace it with:

    .prepend('<img border="0" />')

  14. Tom On 31st January 2009 at 00:01

    I am having the trouble that Ant had where after you hover over more than once, it no longer works. Ferex posted a fix:

    if ($.browser.msie) { $$.css({ ‘position’ : ‘absolute’, ‘left’ : 0, ‘background’ : ”, ‘top’:0 }); }if ($.browser.mozilla) { $$.css({ ‘position’ : ‘absolute’, ‘left’ : 0, ‘background’ : ”, ‘top’:this.offsetTop });

    How and where do I put this in to fix it?

    Any ideas to fix my problem? Thanks!

  15. Emanuele On 4th February 2009 at 09:02

    i found a problem with this fx in a site i’m doing.

    http://www.fopp.it/kris/test/palindromes.html

    the three main button in the bottom plays tricky with opacity, i mean that after pointing the mouse a couple of time over the buttons, the one in the middle (”bio”) doesn’t start the fade out, but remain on the opacity state.

    Do anyone please can suggest a solution? am i wrong with something in the html/css?

    Thank you and bye!

  16. mark On 5th February 2009 at 17:02

    hows this work now that IE8 is (nearly) on the the scene, Im using RC1 and it is positioning the origonal image out of place, so i can see both images..

  17. Omdu On 10th February 2009 at 15:02

    Awesome script. Works well on all major browsers. Thank you.

  18. applet On 16th March 2009 at 13:03

    Thanks for this really good code, but ive copied the single image technique verbatim and it doesnt work in IE6 or IE7 - whats going on?

    as for the border issue css =

    img { border=”0″ outline=”none”}

    (latter being for Mozilla/FF showing a dashed line if its a link)

  19. Brent Kerr On 22nd March 2009 at 15:03

    Brilliant script!

    I had a few problems with the layout in IE like some other people mentioned. Ferex’s solution didn’t quite work for me so I adjusted it slightly (removed top : 0 for msie).

    Here’s the entire fix.

    Replace this:

            if ($.browser.msie || $.browser.mozilla) {
                $$.css({
                    'position' : 'absolute', 
                    'left' : 0,
                    'background' : '',
                    'top' : this.offsetTop
                });
    

    With this:

            if ($.browser.msie) {
                $$.css({
                    'position' : 'absolute', 
                    'left' : 0,
                    'background' : ''
                });
            }
    
            else if ($.browser.mozilla) {
                $$.css({
                    'position' : 'absolute', 
                    'left' : 0,
                    'background' : '',
                    'top' : this.offsetTop
                });
    
  20. Brent Kerr On 22nd March 2009 at 15:03

    @mark

    I managed to get this working in IE 8 with the fix above :)

  21. Brent Kerr On 30th March 2009 at 10:03

    Okay, after alot of messing about, I finally found a way that works for me. I have tried both your methods and Greg J’s similar method (http://greg-j.com/static-content/hover-fade-redux.html). The problem originally was an Opera 9.64 rendering issue. It completely destroyed the page, fading didn’t work and there were performance issues.

    All I wanted was an image link that faded into another image on hover. After fixing the Opera issue (requiring position: absolute on the span), IE 6 and 7 broke. For some reason, the links were no longer clickable. So I gave up and started from scratch. Here’s my code that seems to work fine for me:

    Jquery: $('img').hover( function() { // Hover on $(this).stop().fadeTo(250, 0); }, function() { // Hover off $(this).stop().fadeTo(250, 1); });

    HTML:

  22. Brent Kerr On 30th March 2009 at 10:03

    Can’t have HTML I guess. Basically I had have an anchor link with the background-image set in the style (the ‘on’ image). Inside the anchor is the img tag which is the ‘off’ image.

  23. Frank Doring On 30th March 2009 at 22:03

    Brent, your code works beautifully (Mac Safari, Firefox, current versions) and does some flickering in Mac Netscape 7 (who cares?). I trust it works in the IE world. Is there a way to exempt an image from being faded out (other than by loading it as its own background)?

  24. Frank Doring On 1st April 2009 at 17:04

    One curious observation: when the images get small (below 13px in height, while they are around 30px wide, gif’s), they don’t line up properly in Safari and Firefox Mac. Any idea why that is?

  25. Mick Tomlinson On 11th April 2009 at 08:04

    Can anyone please describe how to apply this to a text link - ie as a mouseover event?

    Many thanks.

Comments are now closed.