Grow your CSS skills. Land your dream job.

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

Comments

  1. Sandy
    Permalink to comment#

    Great stuff, as usual. Exactly what I spent a good chunk of yesterday afternoon failing to figure out.

  2. Nick
    Permalink to comment#

    Nice tut .. quick point JQuery version breaks in chrome if you click the “Button”

  3. shoutyman
    Permalink to comment#

    A small thing, but re Way The Third (which would have been my instinctive way of solving the problem), personally I’d go for unpolluted HTML and create the span element in the script instead…

  4. Permalink to comment#

    @SHOUTYMAN +1

    like

    $(".arrow").append('<span />');
    • Permalink to comment#

      This :)
      Try to avoid unneeded markup, especially if you’d just use it with Javascript enabled.

    • Permalink to comment#

      Might be me but that’s still HTML.
      So you’d have the same amount of HTML as including it in the HTML file itself in the first place. AND you get a piece of extra JavaScript.
      I’m all for cleaning up code. But your solution is still document.write(‘bla’) to me.

      Using pseudo elements would be the proper way of adding visual stuff, but as Chris pointed out, those can’t be animated(yet)

    • shoutyman
      Permalink to comment#

      @Rene

      I’m not sure two extra characters (“append” cf. “find”) is worth calling “extra Javascript” :) …and the served HTML is smaller and cleaner, even if the final document model isn’t.

      Making that change means anyone not running Javascript won’t receive a polluted document. Not that it’s a big deal in the case of an empty span, but as a principle I’d always leave the unscripted content as clean as possible.

      Also, and this is more important that a few characters or empty elements here and there, by generating the element in the script you’re removing a dependency between the content of two separate files. The behaviour is now collocated with the content required to drive it. That’s a significant win for maintainability and that’s A Very Good Thing.

      (In fact I’d normally be inclined to build the behaviour-specific CSS in the script as well, though that’s a marginally less straightforward discussion that I’d rather not get into right now.)

  5. Great walkthrough, Chris. That’s exactly how we handled the navigation on the new United Pixelworkers site. We went with method #1.

  6. Hey Chris, great tutorial, this one will come in handy.

    Unfortunately your demo has a little bug. The jQuery button works if I hover on and off of it slowly but if I go on and off of it quickly it stops working.

    I am experiencing this in Firefox 3.6.4

    Thanks,
    Colin

    • Ah yes I see. To fix that you could not use .fadeIn and .fadeOut but use something like

      .animate({ opacity: 1 }); and .animate({ opacity: 0 }); – That way you are animating to specific values. The fade functions are trying to be helpful by animating to the previously set value, which if they don’t finish, aren’t completely 1 or 0.

    • Permalink to comment#

      You could also use the .fadeTo(’1′) and .fadeTo(’0′) jQuery functions if you had some angry feelings against .animate()…

      Just throwing it out there :D

      Happy v-day!

  7. Permalink to comment#

    Great techniques! Thanks. I think I’m going to look into using the jQuery version for an upcoming project.

  8. Permalink to comment#

    Great tutorial, recently used one of your pre-done sprites and have turned it into a template for re-use, this is a great addition :-))

  9. Permalink to comment#

    Like the second way best, and than the first.
    But…
    I really think the first way more traditional and more instant to understand.
    And the second one seems more tricky.
    Well, jQuery is not welcome for a simple transition :-(

  10. Clare
    Permalink to comment#

    I’m curious, I’ve seen elsewhere that CSS3′s ease[-in] transition can also be used to transition between states in a sprite (e.g. hovering a button). While in the theme I’ve linked to it has a sort of an optical illusion feel as it transitions, I’ve seen similar effects on buttons, icons, links and so forth that are more subtle. If I remember correctly they too used transitions and delays to achieve this alone.

    Wouldn’t this, with maybe a jquery (or whatever library you choose) fallback be better than the opacity or ‘:after’ pseudo attribute methods (I’m aware of the jquery method provided as the third example)? Just wondered what the advantage was – if any. I’ll have to go back and have a proper read of the article, I expect.

  11. Permalink to comment#

    Thanks for the great tutorial. Well written and easy to understand, definitely going to use this in future projects!

  12. The pseudo element solution will be great fun when it has greater browser support, especially as it allows for you to apply the opacity property to the generated content without affecting children of the main element. Good for navigation buttons containing web text and the like. I have an example of what I mean here.

    Shame only FF4 will do it though. Come on Webkit contributors, get your skates on!

  13. I like the number one

  14. Permalink to comment#

    hahaa the first one is clever, simple yet i havent even thought of that!

  15. Permalink to comment#

    Thanks i was looking for the same script..

    Thank you

  16. Permalink to comment#

    Cheers for a great tutorial! Defo gona use this in the future!

  17. Permalink to comment#

    Great tuto, I ‘ll try with the span!
    Merci beaucoup, je vais essayer la technique avec le span.

  18. I tried figuring this out a long time ago, but I failed. It’s nice to see someone figuring this out with such ease. And giving such bulletproof (well, almost) solutions.

    With the advent of the finalization of the CSS 3 spec (including animating pseudo-elements), Way The Second would me the technique for me.

    Chris, I think you should implement this technique somewhere on CSS Tricks, like for the reply button on comments. It usually is abrupt, but with this technique, you can make it transition to the darker version (with maintaining the speed of sprites).

    Just a thought =). By the way, you could do Way The Third completely in jQuery by creating the span tags through jQuery.

  19. Permalink to comment#

    oh yes very nice! i love it.. i’ve been waiting for a new cool trick that i can put on a website and this is awesome. it’s clean and sexy!

    thanks chris. you run this shit! : )

  20. Martijn
    Permalink to comment#

    interesting! I did a similar thing for http://www.nieuwbestand.nl some time ago using jQuery. The solution is very similar.

  21. AyexeM
    Permalink to comment#

    I am seeing inconsistent results across all major browsers for all 3 techniques. Hopefully this can be common place in the future, but until then I’m sticking to proven methods.

  22. Wow seriously amazing tutorial guys! I haven’t been using sprites with CSS3 very much so I can’t say I fully understand where all of your code is coming from. However you present everything in such an elegant way, all of your instructions are very simple to understand and follow.

  23. Permalink to comment#

    Chris, great tut! Thanks for figuring this out.

  24. arnold
    Permalink to comment#

    3rd sample is not working in Chrome and FireFox?

  25. deos
    Permalink to comment#

    interesting ideas, nice article

    thats how i would do the js with mootools (just to add some good js here :P)
    not tested btw, just quickly scribbled together

    (function(){
    var arrow = document.getElement(‘.arrow3′),
    span = arrow.getElement(‘span’).fade(‘hide’);
    arrow.addEvents({
    ‘mouseenter’: function(){
    span.fade(‘in’);
    },
    ‘mouseleve’: function(){
    span.fade(‘out’);
    }
    });
    })();

  26. Permalink to comment#

    Something simillar used in tutorial from nettuts in this example (appstore button)

    http://nettuts.s3.amazonaws.com/899_leaflet/Source/index.html

  27. earnur
    Permalink to comment#

    I like to use third solution :)

    But have some problem with using it when there is need for multiple buttons:

    When placing two <a> tags one above other like this:

    <a href="#" rel="nofollow">twitter&l;t/a>
    <a href="#" rel="nofollow">facebook</a>

    then between two images is a space (see here: http://earnur.cba.pl/projects/blog/test.html)

    But if I will write it in one line:

    <a href="#" rel="nofollow">twitterfacebook</a>

    it showing images properly (http://earnur.cba.pl/projects/blog/test2.html).

    I am always making my HTML files in structural way, not in one line to preserve cleariness, but here I don't know, how to delete that white space… :/

  28. Permalink to comment#

    why do you put “text-indent: -9999px;” ?

    • To hide the text as it is replaced with an image, without the text-indent, “Arrow” would be displayed above the background arrow image. The negative is used to push it to the left because if it was just text-indent: 9999px, it would move to the right and thus a horizontal scrollbar would be created.

  29. Andrew
    Permalink to comment#

    Doesn’t appear to work in firefox for mac

  30. Amazing just the article I was looking for.

    Thanks very much very handy.. CSS Tricks strikes again!

  31. It looks like the JQuery button breaks in Safari and Firefox if you hover on and off a few times :-(

  32. Hi, I’ve found a workaround for “dissolve” transitions on background images:

    http://jsfiddle.net/4a5RV/1/

    It makes use of CSS content and works only on solid backgrounds… but works on Gecko & Webkit browsers :-)

  33. Permalink to comment#

    I like option1 andam using it on a few of image hovers. Works great and I dont mind the transition of the hover when it doesn’t work in some browser instances but then my hover state doesn’t have a ton of contrast. Thanks for posting!

  34. Sean
    Permalink to comment#

    I can’t figure it out, i’m a noobb at coding… I have a button with 3 states that uses css image sprites, but I can’t figure out how to incorporate this effect in…

    Heres my code..

    body {
    background-color: #d3d3d3;
    }

    ul.button {
    margin: auto;
    list-style: none;
    padding: 0px;
    }

    .displace {
    position: absolute;
    left: -5000px;
    }

    ul.button li {
    margin: auto;
    float: left;
    }

    ul.button li a {
    display: block;
    width: 183px;
    height: 68px;
    background: url('sprite.png');
    }

    ul.button li.download a {
    background-position: 0 0;
    }

    ul.button li.download a:hover {
    background-position: 0 -68px;
    }

    ul.button li.download a:active {
    background-position: 0 -136px;
    }

    #allbutton {
    margin-left: auto;
    margin-bottom: auto;
    margin-right: auto;
    margin-top: 50px;
    width: 183px;
    }

    Download

  35. Henri
    Permalink to comment#

    How about the span position -50px?
    Way The First (Reasonable Progressiveness): Is the buttons always in active state in IE8 or should there be a hack if IE8 then Span
    background-position: 0 0; and
    .arrow:hover span { opacity: 1; background-position: -50px 0; }

  36. Ben
    Permalink to comment#

    Hi, I’m able to do both the First and the Second way successfully, however, I am using a sprite that has some parts that are filled but when hovered over, become transparent.

    I noticed that all this code does is overlap the two backgrounds. This is a problem for me because the inactive state can be seen underneath the active state. Is there anyway I can make it so that the inactive state’s opacity becomes 0 when hovered?

    Sorry, I am a novice in CSS.

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".