Image Cross Fade Transition

11Apr

Background

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.

4 Trackbacks/Pingbacks

  1. Pingback: Css ve Javascript Örnekleri at katodivaihe on April 13, 2008
  2. Pingback: Fatih Hayrioğlu’nun not defteri » 17 Nisan 2008 web’den seçme haberler on April 17, 2008
  3. Pingback: Image Cross Fade Transition « Xyberneticos on April 21, 2008
  4. Pingback: NETTUTS - Web development tutorials and links - Best of the Web - April on May 1, 2008

22 Comments

  1. Tim Nash
    April 11, 2008 at 16:02

    The second example doesn't seem to work in Firefox 2.0.0.13 first time (the pencil version are shown beneath the painting and when you mouse over them they vanish) but when I refreshed the page and when I checked in IE worked fine.

  2. Remy
    April 11, 2008 at 16:11

    @Tim - thanks for spotting that. It's being cause because jQuery runs it's ready function (rightly) as early as possible. In particular, before images have been loaded.

    As such, the line that reads this.offsetTop is zero when you run from an empty cache - so the images appear in the wrong place.

    I've changed the example to use window.onload for now (so if you clear you cache, you'll see it working again) and I'm following up with the jQuery community to see if there's a way around this.

  3. Mika Tuupola
    April 13, 2008 at 17:25

    Good stuff!.

    jQuery equivalent for waiting until page is ready (DOM and all elements have been loaded is:

    $(window).bind(‘load’, function() {...}) ;

  4. Remy
    April 13, 2008 at 18:22

    @Mika - thank you! That's been bugging me for a while now. I'll update the demo and code examples to use this.

  5. caruso_g
    April 14, 2008 at 23:29

    Always clear and interesting in your real world examples. Thanks Remy. I hope you could find more time for these great tutorials.

  6. Roland
    April 16, 2008 at 06:41

    Great Screencast!

    Which software did you use to make this screencasts? Especially how did you manage to have the full menubar in in? Did you resize your screen resolution to 800x600px to make this screencast? Thank you :-)

  7. Remy
    April 16, 2008 at 08:09

    @Roland - I use 'iShowU' and have a separate user set up that runs in 800x600 mode, which gives me the full screen during the screencast.

  8. Mishka
    April 16, 2008 at 10:25

    Nice stuff. Your third example with pure CSS solution works well in FireFox 3 beta 5 as well. How is that possible? Did Mozilla developers decided to implement this kind of properties? But anyway, how it works if it's written with -webkit prefix??? strange.

  9. font
    April 16, 2008 at 11:57

    The best tutorial on creating a cross fade on jpegs!

  10. volkan senturk
    April 17, 2008 at 16:28

    thanks for this tutorial

  11. Jason
    April 20, 2008 at 19:09

    Brilliant! This is exactly what I need for a project that I'm working on right now.

  12. J2
    April 25, 2008 at 01:26

    How do you do this technique but using divs? I want to have a div that contains some content (i.e. thumbnails, text, etc.) and when i click on a link, or thumbnail, the content div fades out and a new div fades in (could be the large version of the photo, or more detail, etc.). I know it's a bit off topic on this thread but I've been hunting and found nothing so far.

  13. Joe
    April 29, 2008 at 05:59

    Hi. I have a couple questions. I'm trying to use this technique on a site in two different ways. One, I'm using it in a menu, to fade the menu buttons on hover. I'm also using it to fade a picture and when you click on the picture it goes to a gallery page. Both are working great in firefox, but not in IE7. The menu is wrapped in an unordered list and a line element, so this may be causing some issues (although like I said, not in firefox). Anyway, the menu button's background image is not getting loaded correctly, so the button just fades away, with no other image fading in. Also neither the menu buttons nor the image will correctly act as a link. No ability to click on it at all. Any suggestions? Or just general things to watch out for if you are customizing this type of thing? Thanks

  14. 123mb06
    May 1, 2008 at 11:08

    i tried using the html code above but i can't make it work properly... it just appears as two different images... wat do i do?

  15. mupo
    May 2, 2008 at 13:19

    Very good, thank you! In my case i had to remove the style attribute after the fadeTo-Out function, because sometimes the opacity remained in the style (viewable with firebug):

                // on hovering out, fade the element out
                 ...
                    fade.stop().fadeTo(250, 0, function(){
                        fade.removeAttr('style');
                    });
                }
    
  16. Fer
    May 7, 2008 at 11:14

    This is great! Thanks a lot! Question: I want to try this effect on hovering a container in which I have the image, Is this possible? How? Sorry if I'm asking a little too much.

    Anyway, great article :)

  17. Fer
    May 9, 2008 at 11:44

    I've read a little more about selectors in jQuery and already figure out :)

  18. adam
    May 9, 2008 at 16:34

    Could someone explain how to implement the external JS file in the "taking it further" fashion? The demo has so many other things going on I cant suss out how to implement it. I have the basic version working well in FF but not in safari, so seems from the tute that the plugin is needed to give safari some special treatment. Anyone interested in making a more basic demo for the plugin version? Thanks!!

  19. adam
    May 9, 2008 at 16:55

    OK I figured out how what was awhack- the function in the JS file is called crossfade not just cross. So, now its working correctly in FF as it was before, but still does not perform the fade in Safari- it appears that the BG image shows but the SRC image is stuck at 0 opacity or just not being positioned correctly. Is this just an issue with safari 3.1.1? URL for ref. is:

    http://pureplayclothing.com/new/products/womens

  20. Remy
    May 9, 2008 at 17:50

    @adam - I've been having a look at your code, and running it offline. It's something to do with the jcarousel. I've tried running the page without the jcarousel and it works, so there must be some CSS that's causing it to be a problem in Safari.

  21. Geir
    May 11, 2008 at 16:10

    Thanks for the tutorial and plug-in, but.. I didn't make it work ( The single img technique). Maybe I'll get it later..

  22. Geir
    May 11, 2008 at 16:31

    Ok. I made it work. Used the script from the live demo, but I don't really know what the difference was.. Thanks again!

Leave a comment