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!

Tagged: , , ,

§ 7 Responses to Animating and transitioning display:none elements

  • 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.

  • see7 says:

    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

  • David Zbarsky says:

    “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!

  • 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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

What’s this?

You are currently reading Animating and transitioning display:none elements at JAWS.

meta

%d bloggers like this: