Fade Image Into Another (within a Sprite)

Published by Chris Coyier

Creating an image rollover is pretty easy with CSS. Give the element a background-image, then on it's :hover, change the background-image. It's best practice to combine both images into one and shift the background-position rather than use two separate images, that's the idea of CSS sprites. But what if you want to fade one image into another, not just have an abrupt shift?

The solution there is to create an additional element on top of the original with zero opacity and the background-position already shifted into place. Then on the :hover, fade in the opacity. Here are three ways to do that, using a little arrow graphic.

(Arrows via Ask Differently)

View Demo

Way The First (Reasonable Progressiveness)

Drop a span inside the original element. The span will essentially be the hover state. I know, extra markup sucks, but we need an additional element to use and pseudo elements won't transition in most browsers.

<a href="#" class="arrow">Arrow<span></span></a>

The arrow itself will use basic CSS image replacement and the background positioned in the default position.

.arrow {
	display: inline-block;
	position: relative;
	text-indent: -9999px;
	width: 36px;
	height: 36px;
	background: url(sprites.png) no-repeat;
}

The relative positioning used there is for the span, which we'll absolutely position to be the exact same size. It's background position will be shifted into place for the rollover, it's opacity set down to zero, and the CSS3 transitions put into place.

.arrow span {
	position: absolute;
	top: 0; left: 0; bottom: 0; right: 0;
	background: url(sprites.png) no-repeat;
	background-position: -50px 0;
	opacity: 0;
	-webkit-transition: opacity 0.5s;
	-moz-transition:    opacity 0.5s;
	-o-transition:      opacity 0.5s;
}

Then on hover, the opacity comes up:

.arrow:hover span {
	opacity: 1;
}

Way The Second: Extra Progressive

The only reason we are using the span above is because (at the time of this writing) only Firefox 4 is doing transitions on pseudo elements. It's reasonable to think in the future WebKit and other browsers will support this as well. So we could clean up the markup like this:

<a href="#" class="arrow">Arrow</a>

Then the rest of the CSS is exactly the same, only we use :after instead of the span.

.arrow:after {
	content: "";
	position: absolute;
	top: 0; left: 0; bottom: 0; right: 0;
	background: url(sprites.png) no-repeat;
	background-position: -50px 0;
	opacity: 0;
	-webkit-transition: opacity 0.5s;
	-moz-transition:    opacity 0.5s;
	-o-transition:      opacity 0.5s;
}
.arrow:hover:after {
	opacity: 1;
}

Way The Third: Legacy Support

Some browsers don't support pseudo elements or transitions whatsoever. If we want to make this as cross-browser perfect as possible, we'll have to use the extra HTML and rely upon JavaScript (jQuery...) to help. We're back to the span.

<a href="#" class="arrow">Arrow<span></span></a>

The CSS is basically the same, but simplified as we aren't handling any of the hover or fadein stuff here.

.arrow {
	display: inline-block;
	position: relative;
	text-indent: -9999px;
	width: 36px;
	height: 36px;
	background: url(sprites.png) no-repeat;
}
.arrow span {
	position: absolute;
	top: 0; left: 0; bottom: 0; right: 0;
	background: url(sprites.png) no-repeat;
	background-position: -50px 0;
}

jQuery will handle the fadein. First we'll hide the span, then attach mouseenter and mouseleave events via the hover function that do the fading.

$(function() {
	$(".arrow")
	.find("span")
	.hide()
	.end()
	.hover(function() {
		$(this).find("span").stop(true, true).fadeIn();
	}, function() {
		$(this).find("span").stop(true, true).fadeOut();
	});
});

All three examples:

View Demo