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)

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:
- Two Image
- Single Image
- 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:
- Wrap the image in a
span - Insert a new image, whose source is the
background-imageof our start image - Position the new image so that sits directly behind the starting image
- 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.
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.
You should follow me on Twitter here I tweet about jQuery amongst the usual tweet-splurges!
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>

Play QuickTime version
Play Flash version

Ross B On 9th May 2009 at 03:05
//you dont need to define a new MSIE browser for 8. This will mess it up and not work (wont work in IE7), and by not defining one for 8 and only for 7 it actually does work for both
//this works for IE7 and IE8 …hope this helps
Acronyms On 12th May 2009 at 02:05
No iframe but still all six images were shown. The glitch was gone when opened directly.
daryl On 12th May 2009 at 16:05
i found that IE 8 behaves differently from IE 6 and 7, and requires that the top offset be declared explicitly, not sure how else to do this. also IE 6 and 7 should also have the top declaration of 0, like opera.
Will Harrell On 15th May 2009 at 14:05
Hey Remy I copied your demo to tweak with it and try and learn as much as I can. Thanks for your help!
I’ve come across some strange behavior. My copy works fine even if you hover and unhover very quickly long enough. However, if I do that for a couple of seconds the hover will not respond for 5-7 seconds and then it goes back to normal. Any thoughts?
Thanks
Devfolio On 20th May 2009 at 10:05
Uhm, this is not working correctly.. (ie 7/8).
Rebecca Perry On 21st May 2009 at 22:05
Thanks for the code & tips. works fine for me in IE7, FireFox & Safari. Have not tested in IE6 (IE6 needs to go away!)
Having issues with the single image technique, no big deal though… thanks again!
Nathan On 4th June 2009 at 10:06
‘top’ : this.offsetTop seems to give problems with me and my IE’s… Is there a way around this?
dj_maxx On 14th June 2009 at 07:06
I have found strange rendering bug in Opera 9 (9.64), while IE and FF acting normal. Pictures with “fade” class are ripped while mouse hovers over. I can’t expain a bug more detailed due to antispam filter on this blog (all URL and screenshots marked as spam). Visit my site to reproduce a bug.
DNUTZ On 19th June 2009 at 12:06
Hi, I’ve read the comments and i didn’t find anything that has to do with my problem. Could someone please help me with this. I’m trying to use the same code for more than one jpeg. But i can’t fix it.(i’m using the 2 pics method) Does anyone have any idea on how to use more than 1 picture. It shouldn’t be to difficult but i’m pretty new a this.
sirea On 19th June 2009 at 18:06
Hello, I have some problems using this “method2″. I have it for fading pictures in separate divs and everything works fine but when I’m trying use this technigue in dropdown menu “li” elements (where I have some pictures with this effect) join together in one line element. Why? How should I solve this problem? Please, give some advise…
Pat On 27th June 2009 at 18:06
I’m surprised no one posted any comment. I recently finished a menu using css and some javascript and I was wondering how I could make a neat hover effect to the same button, but bigger.
I gotta admit, I’m still a newb when it comes to jQuery, it’s sooo messed up. I’m still reading the code. Atm, I have two functions covering all my menu buttons by passing param’s, since it’s really effective I haven’t changed it yet.
Sigh, there is so much work behind a more “interactive” website…
P.S Thanks for the tutorial.
Elemental Web and Mobile Solutions On 13th July 2009 at 14:07
Thanks, i really like the way JQuery does the manipulation.
Gustavo On 15th July 2009 at 15:07
Great tutorial! Im looking for something just like it but instead on mouse over (hover) i need it to be on click. Do you think that by changing the hover to Onclick will do the trick.
Thanks so much for you help.
Jason Fletcher On 31st July 2009 at 11:07
The Single Image Technique is quite beautifully executed. Bravo!
Unfortunately when you attach a hyperlink to the photo, the background image is given a border (even if you set a zero border to the html attributes). Any idea on how to fix this?
Jason Fletcher On 31st July 2009 at 11:07
Single Image technique - fix for the background image hyperlink border.
Annoying yet working fix. Appending the following CSS will result in no image borders site-wide… Anyone know a better implementation?
img { border: none; }
Danielle On 5th August 2009 at 00:08
I followed along and copy and pasted all three methods using the code and markup and nothing! What am I doing wrong? I’m using Dreamweaver (but I don’t think that should make any difference when I go to preview in my browser). I figured these tutorials are pretty much touch and go, but maybe I’m missing something?
mr.c.note On 17th August 2009 at 12:08
nice script, works perfectly in everything except IE for me. i think its something in my site’s reset CSS code that breaks it in IE as the demo works fine. any ideas? just noticed the fix on the previous page…all good
Chris On 21st August 2009 at 17:08
Using PNGs with Alpha transparency and the two-image method, but all versions of IE turn transparency black during the fade.
Solution:
Borrowed from this URL- http://stackoverflow.com/questions/1004973/how-to-maintain-png-alpha-transparency-when-using-ms-filter-property
Natasha On 27th August 2009 at 05:08
Hi, thanks for the tutorial, but when I put an link around my image, even though my image is set to border:0px; - it shows a border. I’m using the ’single image’ technique.
Alberto On 27th August 2009 at 13:08
Hello, great script!
I have the “border issue”, I have tried all the suggestions in this post but no-one works. Did you find any solution?
Dj On 6th September 2009 at 22:09
i have link subscribe in as reader now it is no showing in center in safari and opera .span positioning is not working in safari and opera but ff & ie works perfect. plz check and tell me http://techieinspire.blogspot.com djroshan007@gmail.com
Saurabh On 8th September 2009 at 09:09
Hi, Can I use this code in my website? Are there any Licensing obligations?
Thanks, Saurabh
Fernando Suárez On 9th September 2009 at 23:09
Thanks a lot for the article, Remy.
I have no experience with jQuery, and I am wondering which technique I have to take to develop the main image above (the one to access to Flash or Quicktime), or the ones appeared in blogs like http://siteinspired.net (in every image). Is it the same process as you explained here?
Thanks a lot again.
Benjamin M. Brown On 18th September 2009 at 22:09
Very cool tricks.. vid tutorial helped a ton, thanks.
-B
Dave On 1st October 2009 at 22:10
Great tut, but still having a border issue. Or more specifically, an underline issue. I can get rid of the border in Firefox, but in Chrome there always remains a blue underline beneath the image. I assume it’s a link border. Anyone have any clues?