Animating and transitioning display:none elements
11 June, 2013 § 7 Comments
The current release of Firefox introduces a nice animation when opening and closing the Find bar. Upon opening the Find bar, the toolbar fades in and slides up. Exiting the toolbar will make it fade out and slide down.
As you may or may not know, the user interface for Firefox is implemented using XUL+HTML/CSS/JavaScript.
We use the XUL hidden
attribute, which is very similar to the HTML5 hidden
attribute, to hide the Find bar when it is dismissed. The presence of the hidden
attribute sets display:none
on the referenced element. If you’ve ever played around with CSS Transitions or Animations, you’ve probably noticed that display
is not a transitionable property.
To maintain add-on compatibility, as well as code simplicity, we wanted to keep using the hidden
attribute. Doing so also allowed us to leave the JavaScript unchanged.
This poses a hard problem. If display
is not transitionable, how could we animate the behavior here? It turns out that visibility
is a transitionable property.
The various states of visibility
are atomic. There is no defined behavior for what should happen when an element transitions from visibility:hidden
to visibility:visible
or visibility:collapse
. However, we can use a zero second duration to make the change happen at the instant that we want.
This brings us to our solution:
findbar { transition-property: margin-bottom, opacity, visibility; transition-duration: 150ms, 150ms, 0s; transition-timing-function: ease-in-out, ease-in-out, linear; margin-bottom: 0; opacity: 1; } findbar[hidden] { /* Override display:none to make the transition work. */ display: -moz-box; visibility: collapse; margin-bottom: -1em; opacity: 0; transition-delay: 0s, 0s, 150ms; }
To explain how this works we’ll start by looking at findbar[hidden]
, since it’s the default state for the Find bar.
When it is hidden, we will force it to be displayed (using display:-moz-box
, which is an internal-to-Gecko display
value, you could replace this with display:block
or display:inline
as needed). We then set visibility
to collapse
, which will effectively make the element invisible and also not consume any space (except for margins, which is important here). We then use a negative margin-bottom
to force the element to be placed just out of view of the browser window. Setting opacity
here is used to fade the element in and out. I’ll describe the transition-delay
last.
When the Find bar is shown, we set margin-bottom
to zero which will slide the toolbar up. We also set opacity
to one which will make it fade in. The opposite happens in reverse when the Find bar is dismissed.
The key here is the transition-delay
that is set in the findbar[hidden]
case.
When the Find bar is hidden and transitions to being visible, we don’t have a transition-delay
applied. This causes the visibility
to change immediately at the beginning of the animation. The Find bar becomes visible and then it slides and fades in to view.
When the Find bar is visible and transitions to being hidden, we delay changing the visibility
until the margin
and opacity
transitions have completed. Running the visibility
transition immediately in this case would cause the element to disappear instantly, which would ruin the animation.
That explains how I implemented the Find bar transition in Firefox without affecting themes or having to change JS. It’s a pretty cool technique for showing and hiding elements, and has opened the doors to implementing some other types of animations in the Firefox user interface. Let me know what you think! 🙂
Update: I’ve been told by Frank Yan that this won’t work on non-XUL content since display:-moz-box
is needed to get the visibility:collapse
to work as needed. Still a cool technique to see!
No, you’re using the XUL hidden attribute:
https://developer.mozilla.org/en-US/docs/XUL/Attribute/hidden
First mention I can find of it dates from 2000/05/29.
(Oh, and there’s a property to go with it too, but the attribute showed up first when I searched for it.)
Thanks for the correction, I’ve updated the post to fix the error.
what I do not understand why you use visibility:collapse as AFAIK browser support is almost zero (see e.g. http://www.quirksmode.org/css/visibility.html). You can do it without (simple visibility: hidden) but would be useful to have but seems mostly you won’t
“There is no defined behavior for what should happen when an element transitions from visibility:hidden to visibility:visible” – This is actually false. When you interpolate between those, you always get visibility:visible except for the hidden endpoint.
Spec: “visibility: if one of the values is ‘visible’, interpolated as a discrete step where values of the timing function between 0 and 1 map to ‘visible’ and other values of the timing function (which occur only at the start/end of the transition or as a result of ‘cubic-bezier()’ functions with Y values outside of [0, 1]) map to the closer endpoint; if neither value is ‘visible’ then not interpolable.”
Cheers!
Yes, that is worded much better than how I wrote it. Thanks!
Oh, hvis is pretty sweet… So far I’ve relied on “opacity: 0; pointer-events: none;” to make things fade away… Somehow I never thought it was a very good solution 🙂
But having to manually set “display: none;” after the transition is quite annoying.