CSS3 transitions using visibility and delay
Apr 21, 2011One of the most common CSS3 transition animations used by developers in the coming years will undoubtedly be making elements appear and disappear via the opacity
property. What previously was only possible using JavaScript can now be easily described in significantly less bulky CSS code:
div { opacity:0; transition:opacity 1s linear;* } div:hover { opacity:1; }
* Note: Because the spec is still in flux, most browsers require a vendor-specific prefix such as -webkit-transition
for Chrome/Safari, -moz-transition
for Firefox and -o-transition
for Opera to make CSS3 transitions work. For the sake of simplicity in the article, I will be using just plain transition
in the examples.
The most exciting thing about CSS3 transitions is that, by design, they naturally fall back to the normal non-animated transitions for browsers which don't support them. That means you can start using them in production designs today! Using opacity
to fade in elements is great for all kinds of novel website effects which reduce reliance on JavaScript for things developers everywhere are already doing.
Take for example, the drop-down menu. For years developers have been creating snappy menus using only CSS :hover
effects. It would only make sense to start applying for opacity transitions to these menus to make them just a little bit more slick. However, here is where we run into our first problem with using opacity
.
An element with opacity
of zero is still "opaque" to clicks and mouse-overs. This causes serious problems, especially for drop down menus:
div { background-color:#f6f6f6; padding:2px 5px; position:relative: } div > ul { list-style-type:none; margin:0px; padding:2px 10px; position:absolute; left:0px; top:100%; background-color:#eeeeee; opacity:0; transition:opacity 0.5s linear; } div:hover > ul { opacity:1; }
- Menu item 1
- Menu item 2
- Menu item 3
- Menu item 4
It looks nice, but the problem becomes obvious when you try to click the link. The fully transparent menu still appears when we hover any area it covers, essentially preventing all interaction with any content beneath. Not to mention the behaviour itself is quite odd, especially when moving the mouse toward the link from below. What we want is to prevent the menu from receiving hover events when it is fully transparent.
Okay, the display
property can do that with the block
and none
values. Let's give it a try:
. . . div > ul { . . . display:none; opacity:0; transition:opacity 0.5s linear; } div:hover > ul { display:block; opacity:1; }
- Menu item 1
- Menu item 2
- Menu item 3
- Menu item 4
Hey now, we can click that link without the menu appearing! But... it no longer animates in Chrome and Firefox, and it only animates the fade-in part in Opera. When you mouse out of the menu, it just vanishes. Well, this sucks; I guess we can't use display
with transitions if we want them to work reliably cross-browser.
Fortunately, there is another property we can use: visibility
. Elements that are visibility:hidden
are transparent to clicks and hover events, which is exactly what we want. Let's do this!
. . . div > ul { . . . visibility:hidden; opacity:0; transition:opacity 0.5s linear; } div:hover > ul { visibility:visible; opacity:1; }
- Menu item 1
- Menu item 2
- Menu item 3
- Menu item 4
Excellent! Now all the browsers behave the same way. Unfortunately they all behave the same way as Opera: The fade-in works fine, but there is no fade-out, it just disappears. To debug this problem, we need to delve deeper into how CSS3 transition timing actually works.
We have an element, the menu, which switches from hidden to visible on mouseover, and smoothly transitions from fully transparent to fully opaque in half a second. That seems logical, but why is the fade-out not happening when we reverse that process? The key is knowing at what point the switch from visibility:visible
to visibility:hidden
occurs. In both cases, this switch happens immediately.
- User mouses over the element
visibility
is switched tovisible
opacity
transition animation beginsopacity
transition animation ends
- User mouses out of the element
visibility
is switched tohidden
opacity
transition animation beginsopacity
transition animation ends
In the second case, when visibility
switches back to hidden
, it sabotages our whole animation by hiding it. The animation still occurs, it's just hidden from view. We really want the fade-out action timeline to look like this:
- User mouses out of the element
opacity
transition animation beginsopacity
transition animation endsvisibility
is switched tohidden
But how in the world can we delay a style from taking effe... Oh my goodness, CSS3 transitions can do that too! And lucky us, visibility
is one of the select group of CSS properties which are affected by CSS transitions. Let's slap on a visibility
transition!
. . . div > ul { . . . visibility:hidden; opacity:0; transition:visibility 0s linear 0.5s,opacity 0.5s linear; } div:hover > ul { visibility:visible; opacity:1; }
- Menu item 1
- Menu item 2
- Menu item 3
- Menu item 4
Quick explanation: We added a visibility
transition with a 0s
animation time, a linear
animation curve, and we also used the optional delay argument to set an animation delay of 0.5s
. This means there will be a half second delay before the transition takes place, which exactly equals the time taken by our opacity
fade out. And...
... okay the fade-out works, but adding that code ruined the fade-in which was working so nicely before. What gives? The delay we added to the visibility
property is taking effect both during the fade-in and the fade-out. All we've done is made our fade-in action timeline look like this:
- User mouses over the element
opacity
transition animation beginsopacity
transition animation endsvisibility
is switched tovisible
ARGH! Now what? We want the visibility
to change with no delay on the fade-in, but with a half-second delay on the fade-out. Is that even possible? It seems like there is no way to specify different transition properties for the two halves of the animation effect. Is all lost? Must we resort to JavaScript?
There is one more thing we can try. Like in many CSS modules, such as border
and background
, the CSS3 transition
property is actually a shortcut property which groups several properties you can specify individually. One of these properties is transition-delay
. Using this property we want to temporarily set the delay of the visibility
transition to 0s
only during the fade-in portion of the animation. But how do we specify that?
Oh yeah! The fade-in only happens while the user has their mouse over the element. The fade-out doesn't happen until the mouse cursor is removed. So we can apply this special transition-delay
to the :hover
state of the element.
. . . div > ul { . . . visibility:hidden; opacity:0; transition:visibility 0s linear 0.5s,opacity 0.5s linear; } div:hover > ul { visibility:visible; opacity:1; transition-delay:0s; }
- Menu item 1
- Menu item 2
- Menu item 3
- Menu item 4
You'll notice that we only specified one delay time, while we are using two transitions: visibility
and opacity
. According to the CSS3 specification, when there aren't enough delay values to go around, they are repeated to apply to all transitions. In this case, the opacity
transition had a 0s
delay by default so it actually doesn't change.
How about that, it works! With a temporary change in the delay value of the transition, we successfully created a drop-down menu that both fades-in and fades-out very nicely, and also doesn't interfere with content beneath it. But is looking nice the end of the story?
Heavens no! Drop-down menus using :hover
have been blasted by usability experts for years; especially when you start adding in sub-menus. If you move your mouse just one pixel outside of an element in the :hover
chain, the whole menu up to that point will collapse instantly, likely hiding the menu option you were trying to get to.
All that changes with CSS opacity
transitions. When your mouse cursor travels outside a :hover
chain element, its children don't disappear immediately, but rather start the opacity
animation to transparency. Before the animation has completed, the element is still available to receive :hover
events. Therefore if you move you mouse back onto the element quickly, the chain won't collapse and your target will still be available to select. This solves the dreaded "diagonal problem" that plagues so many CSS drop-down menu implementations.
Who knew CSS3 transitions could be for more than just fancy effects? They're a boon for menu usability as well! In the future, I definitely expect we will see many more uses for transitions beyond simply prettying up websites. Perhaps you are tinkering with some already.
⇐ Graphing the NHL 2010-2011 playoff race in HTML5 canvas | Dinosaur Comics ⇒ |