Skip to Navigation

Using CSS3 to Dissolve Between Background Images

At An Event Apart earlier this year, I saw many examples of CSS3 animations and transitions involving manipulating images and objects over time in exciting ways. Recently I was working on a site and saw an opportunity to possibly include a subtle CSS3 transition in the site’s primary navigation. I’d seen size, movement, and rotation animated before, but I’d never seen an exact demonstration of the effect I was looking for, which was a simple dissolve between two images. I thought it might be interesting to outline the thought process I went through in creating what I believed would be a simple addition, but ended up being a bit more complex than anticipated. If you wish to just jump to the full, working example, see the last section, “Putting It All Together.”

<div class="button"><a href="#">Products & Resources</a></div>

What I started with initially was a rather simple navigation bar comprised of text links, each in a container (like what you see above). The text links were each replaced by a background image using CSS, and that background image would change on roll-over. The HTML text itself is moved off the screen, since this client required a specific, not @font-face usable font be used on the navigation items (so the text is actually part of each image). For each of the buttons, I had a background image that contained the normal state and the hover state, and would simply negatively reposition the background image on hover. Below is what one of those images looked like, with the normal state on top and hover state below it.

Here is what that CSS looks like:

.button a { background: url(button_products_resources.png) no-repeat 0 0; display: block; height: 41px; text-indent: -9999px; width: 216px; } .button-fade a:hover, .button-fade a:focus { background-position: 0 -41px; }

 

So, what I had was five buttons that, when moused over, would slightly change in color and add a little white arrow at the bottom to indicate it was selected. At this stage, mousing over the image created the effect illustrated in the animated gif below.

Thinking About Adding in a Transition

At this point, I was pretty happy with the buttons -- they worked in all major browsers (including IE6), but like all roll-over effects created using this technique, the change between the normal and hover states was instantaneous. Looking at the design of the buttons, I thought it might be cool if instead of the little arrow just “popping up” instantly, that it faded in and out. Having had minimal experience with CSS transitions, I thought this would be pretty simple -- I imagined the CSS might be close to “transition: background fade 2sec.” Once upon a time, I worked in video editing, so something along those lines seemed pretty logical, so I felt all I was missing was the correct syntax. After all, in the video editing world, the most common transition (perhaps after a straight cut) is the dissolve. In video editing software, that usually looks something like this.

Well, as it turns out, CSS transitions don’t really work like that. A transition in CSS3 is really a numerical transformation. So, while you can easily transition from margin-top: -10px to margin-top: -20px (and magically watch your item crawl up the screen 10 pixels as it moves from -10 to -11 to -12, etc.), you can’t transition between two non-numerical values like from one image file name to another. One thing I did try was transitioning on background-position, which you can do since it’s a numerical value, but that does not give the desired effect. Instead of fading between positions, it just flips the image slowly. So in this case, when a user hovers over, the button turns over like one of those old alarm clocks where the numbers fall on each other (like in Groundhog Day). That undesired effect is illustrated below.

Getting the Transition Working

Yeah, so that’s kinda cool, but it’s not very subtle. And, honestly, it’s sort of ridiculous looking. So I was back to square one. At this point I considered JavaScript, but ultimately decided there’s got to be a way to do this using just pure CSS, because if there isn’t, there really should be. So, I started thinking about the video editing example and realized that all a fade really involves is raising the transparency of one item that sits on top of another. I might not be able to express a background image as a numerical value, but transparency—that’s easy. That one CSS3 calls opacity.

Using the same markup as above, I changed the CSS. On the anchor I left the background image “normal” state. On its container (the DIV), I gave it a background image of the “hovered” state with the little white arrow. Since the anchor sits on top of the DIV, this results in a stack of two images, with the normal state on top and the hovered state directly underneath it. For the anchor I set the opacity to 1, except on hover it is changed to 0. All that was left to get the animated transition working was to add in the transition CSS.

I used all 3 vendor prefix versions of the transition code, as well as the assumed non-vendored version. Basically what each of these says is “for changes in the opacity property, animate the change over the course of 0.6 seconds, easing in and out of the transition.”

Below is the code, as well as an animated gif of the achieved effect.

<div class="button-fade"><a href="#">Products & Resources</a></div>
.button-fade { background: url(button_products_resources.png) no-repeat 0 -41px; height: 41px; width: 216px; } .button-fade a { background: url(button_products_resources.png) no-repeat 0 0; display: block; height: 41px; text-indent: -9999px; width: 216px; -webkit-transition: opacity .6s ease-in-out; -moz-transition: opacity .6s ease-in-out; -o-transition: opacity .6s ease-in-out; transition: opacity .6s ease-in-out; } .button-fade a:hover, .button-fade a:focus { opacity: 0; }

Doh! What About non-CSS3 Browsers?

Hey, that works awesome. Oh, wait... maybe for browsers that support opacity, but that doesn’t include IE 6, 7 or 8. Since they don’t know what to do with opacity, they ignore it and the button never changes to the hover state. That wasn’t really acceptable -- but my despair was short-lived as I realized that this whole thing started with something that already worked in Internet Explorer, so I just needed to use the same technique that had been working before, but as a fall back.

Putting It All Together

To make it work in IE 8 and less, all you really need to do is add back in the background-position change on mouseover which will work fine since IE 6-8 ignores the opacity property. You could easily include this code in an IE 8 and below stylesheet. However, there probably are older versions of other browsers out there that would need the same fix, so I wanted to keep it within the main stylesheet if possible. The only problem with that: by adding in the background repositioning (which happens instantaneously), it overrides the visual effect of the dissolve we just spent so much time creating for CSS3 capable browsers. After giving this a little thought, I came up with a simple fix -- include the background-repositioning, but for browsers that support transitions, tell it to delay the repositioning for a ridiculously long time.

Below you will see the final code and an animated version of the hover effect. The important changes here are the addition of the background-position change on hover for non-CSS3 transition browsers and the additional background delay added to the transitions. Essentially what the transition statements are saying is “for changes in the opacity property, animate the change over 0.6 seconds; for changes to the background property, animate the change over 0 seconds and delay the transition by 2012 seconds.”

Why so long? Well, that’s over 30 minutes, so that seems like an unlikely amount of time for someone to hover over a link. If they were to hover over the link for more than half an hour, it’s true, they would see a very minor graphical glitch in the animation. But maybe they deserve to see that as a reward for their super-human patience. Regardless, no one is ever going to do that under normal use. Even if they did, the resulting behavior is so minor and unimportant, I don’t see this as a real drawback. I tried setting it to ‘infinite,’ but that didn’t seem to fly. But please leave a comment if you know of a way to literally tell it to delay the animation until the end of time.

As for why I chose 2012 -- well, that’s the year the world ends, at least according to the Mayans, and that seems like a long enough time to me. Plus, I thought Roland Emmerich’s disaster epic was kind of fun and got a bit of a bad rap, and you know, Cusack is good in anything. If you were more of a Kubrick kind of person, you might pick 2001 as your delay. Or, if you prefer your sci-fi a little more coherent or with more Roy Scheider, maybe you’d go with 2010. 8675309 might be an even better choice (Jenny’s number, according to Tommy Tutone), as that would require someone to hover on your anchor tag for more than 344 full days to see the delayed animation. I leave it to you to pick your own delay, but I’d think anything over a minute or two should be fine.

<div class="button-fade"><a href="#">Products & Resources</a></div>
.button-fade { background: url(button_products_resources.png) no-repeat 0 -41px; height: 41px; width: 216px; } .button-fade a { background: url(button_products_resources.png) no-repeat 0 0; display: block; height: 41px; text-indent: -9999px; width: 216px; -webkit-transition: opacity .6s ease-in-out, background 0s 2012s; -moz-transition: opacity .6s ease-in-out, background 0s 2012s; -o-transition: opacity .6s ease-in-out, background 0s 2012s; transition: opacity .6s ease-in-out, background 0s 2012s; } .button-fade a:hover, .button-fade a:focus { background-position: 0 -41px; opacity: 0; }