A Modest Proposal for CSS3 Animations

I've been thinking quite a bit about CSS architecture these days.

One thing in particular that has crossed my mind is how to handle certain situations. For example, we want to hide content on the page and then reveal it (or vice versa). In JavaScript, this is relatively straightforward: get an element, and apply a class or remove a class to change the state of the element. The CSS for that might look something like this:

div { display:block; }
div.hidden { display:none; }

In this case, I've used display:none to hide the content visually and from screenreaders, too.

Adding a visual effect

Now, if I want to add a visual effect, I might use JavaScript to alter a style property from value A to value B (eg. style.opacity) The script would run through the animation and apply the hidden class at the end of the animation.

What if we wanted to offset the visual effects over to CSS3 Animations?

@keyframes fade-out {
    0% { opacity: 1; }
    100% { opacity: 0; }
}

div { display:block; }
div.hidden { display:none; animation: fade-out .5s 1; }

Nice and easy! Or is it? For those that have actually tried this code might be surprised to discover that this doesn't work. It's because once the hidden class is applied to an element, it's immediately hidden with display: none;.

The next thing you might think to do is apply display:block in the keyframes like so:

@keyframes fade-out {
    0% { display:block; opacity: 1; }
    100% { display:none; opacity: 0; }
}

The problem with this is that non-transitionable properties like display are ignored and have no effect.

A Proposition

I propose that the CSS3 Animation specification be changed to allow for this. Keyframes should act like classes being applied to an element. Therefore, the example above is display:block at 0% and becomes display:none at 100%. As a result, the page works as expected for this scenario.

Transitioning properties

I woke up this morning thinking about this further—and I'll readily admit that this next idea complicates things a little bit. I additionally propose that an animation-transition-property property be added. This specifies which of the keyframe properties should actually transition.

@keyframes fade-out {
    0% { opacity: 1; }
    100% { opacity: 0; position: absolute; left: -999px; }
}

div.hidden {
    animation: fade-out .5s 1;
    animation-transition-property: opacity;
}

By setting the animation-transition-property, only the opacity will transition. At the end of the transition, the element is then placed offscreen using position and left properties. The default setting for animation-transition-property would be all which would allow keyframes to behave exactly as they do today. There would also be a value of none that would allow for no property to transition. This would allow for stepped animations to be created.

Reversing animations

My last proposal for CSS3 animations is the ability to reverse an animation. Right now, the spec has an alternate property that allows every odd iteration to animate in reverse but that's not quite what we want. Again, allow me to demonstrate with an example:

@keyframes fade {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

div {
    animation: fade .5s 1;
}

div.hidden {
    animation-direction: reverse;
}

In this example, I have a fade animation applied to the div. By default, it'll fade to 100%. When the hidden class is applied, the direction of the animation is changed and the fade goes from 100% to 0%. This allows animations to be quickly and easily re-used.

Standards Process

Writing a specification is hard and I don't envy those that have to work through these things. It's a balance between making something powerful and making something complicated. I hope that my ideas fall more in the former category than the latter.

Published July 29, 2011
Categorized as HTML and CSS
Short URL: http://snook.ca/s/998

Conversation

22 Comments · RSS feed
Jordan Dobson said on July 29, 2011

But... when you go in reverse does the display: none; or off screen positioning persist from 100% to 1%? Seems like the two somewhat conflict with each other in that sense.

It's almost like you want an "end" (101%) state that can be used to apply things like display:none; when and only when the animations is at the 100% boundary?

Jonathan Snook said on July 29, 2011

@Jordan: Admittedly, the example changes throughout the article. In order to do what you're asking, what you'd actually want is something like this:

@keyframes fade {
   0% { display:block; opacity: 0; }
   100% { display:block; opacity: 1; }
}
div { display:block; animation: fade .5 1; }
div.hidden { display:none; animation-direction: reverse; }

Once it reaches 100%, the animation styles are removed—this is how it currently behaves, too.

Jordan Dobson said on July 29, 2011

I see. I've tried exactly the same things myself and I've had to resort to strange hacks to get a similar effect. I agree on both points. I would love to see this get adopted!

Nathan Ziarek said on July 29, 2011

I've been playing with animations a bit lately, too, and agree that something needs to be done for some certain situations. But … I think a different, and maybe more simple approach is necessary.

Right now, this works:


div.isShown {
    opacity: 1;
    -webkit-transition: opacity 1s linear;
}
div.isHidden{
    opacity: 0;
    -webkit-transition: opacity 1s linear;
}

Swapping the class on that DIV will fade it in and out smoothly. What won't work (and can be critical) is any style property that has a fixed number of values. display is certainly one of those.

My solution relies more on defining properties as either "scalable" (color, font-size), or "fixed" (display, text-decoration) ... (these types of properties might already have names that I'm unaware of).

Scalable properties can be animated as normal. If a scalable property is not included in the transition definition (either explicitly or via an "all" or * nomenclature), then it is treated as fixed.

Fixed properties only apply at the end of a transition. If there are multiple transitions timings running on a single class, it applies at the end of all transitions. Additionally, a fixed property could be "animated" if you wanted it to occur at another time during the overall transition...

An example:


div.isShown {
    opacity: 1;
    display: block;
    -webkit-transition-easing: linear;
    -webkit-transition-property: opacity, display;
    -webkit-transition-timing: 1s, 0s; /* display is transitioned (made block) after 0 seconds, opacity in 1 second */
}
div.isHidden{
    opacity: 0;
    display: none;
    -webkit-transition-easing: linear;
    -webkit-transition-property: opacity, display;
    -webkit-transition-timing: 1s, 1s; /* display is transitioned (made none) after 1 second, opacity in 1 second */
}

I'm using the -webkit-nomenclature since it works, but I think the idea is solid. It would allow you to animate non-scalable elements and set the time at which their effect occurs.

Be interested to hear your thoughts.

Nathan Ziarek said on July 29, 2011

My properties weren't right above and that was bugging me. That'll teach me to try and write CSS without auto-complete!

Code still doesn't work, but at least the property names are right :)


div.isShown {
    opacity: 1;
    display: block;
    -webkit-transition-timing-function: linear;
    -webkit-transition-property: opacity, display;
    -webkit-transition-duration: 1s, 0s; /* display is transitioned (made block) after 0 seconds, opacity in 1 second */
}
div.isHidden{
    opacity: 0;
    display: none;
    -webkit-transition-timing-function: linear;
    -webkit-transition-property: opacity, display;
    -webkit-transition-duration: 1s, 1s; /* display is transitioned (made none) after 1 second, opacity in 1 second */
}
Bridget Stewart said on July 29, 2011

I guess I'm not really clear why it would be preferable to have display:none / display:block able to be animated when the effect is actually possible using different style declarations to make it happen. Opacity works, albeit with the element continuing to take up space in the document flow (leaving behind a big space where it used to be) - but you could animate height or something along with it to make it fade out and appear to be removed from the document flow, right?

Is it just a matter of trying to have the effect take place using fewer style rules to make it so? Not complaining...just wondering.

Jonathan Snook said on July 29, 2011

@Bridget: in the example I used, display:none has advantages because it also removes the element from being read by screenreaders.

Bridget Stewart said on July 29, 2011

Can't that (display:none) be done after the animation is completed? Still, I understand that it would take more than one style rule to make all that happen. If it's just a matter of trying to make more things animatable for the sake of making it easier on authors, I have no real problem with that. hehe

BrianMB said on July 29, 2011

This is a great feature suggestion, which is why it will be ignored by the CSSWG. :P

Jonathan Snook said on July 29, 2011

@Bridget: the only way to do it after the animation is complete is with JavaScript and you'd have to know exactly how long the animation would take in order to do it. With the approach I outline, you could actually do a pop-up dialog entirely with CSS. I believe these features will make it easier on authors while also making it more powerful.

Thomas said on July 29, 2011

@Jonathan: There is no need to know the animation duration, the "transitionend" event takes care of that. But anyway, being able to use CSS only would be great!

Thomas said on July 29, 2011

Whoups, in case of animations it would be "animationend".

Leighton said on July 31, 2011

I'd love to see this added in the spec.

Divya said on July 31, 2011

You can technically animate only one property by setting the 99.9% keyframe rule? http://jsfiddle.net/nimbu/bBRzR/

Tim Hettler said on August 01, 2011

I think the behavior you are proposing is definitely the expected and desired behavior. I'd wager that "do an animation then hide the element" is the most common problem trying to be solved with CSS animations, and should really be addressed in some way within the specification.

The only issue I see with your proposal is that all of the properties become encapsulated within the animation definition, and thus are lost on older browsers.

Perhaps a property that defines whether animations should occur before or after non-animated properties take effect would solve this problem in a more general way. Something like:

animation-order: before|after;

This way, if CSS animations aren't supported, all of the fixed elements still get applied.

Ryan Cannon said on August 01, 2011

I was afraid this post had something to do with eating babies.

Whenever I try to develop with CSS transitions, I find that invariably I want different effects for when the class is added than when it is removed. I also think that some of the problems this solution solves are solved now in other ways—object detection for graceful degradation, and animation events for setting non-transitionable properties. For example: http://jsfiddle.net/yabjH/1/

Ryan Gasparini said on August 02, 2011

I feel that a property such as "myClip._visible = false" from ActionScript is necessary in CSS. The display property deals with the formatting aspect of an element, not whether or not the element should be rendered by the agent. This property also does a disservice developers by requiring some programatic way (limited to JavaScript?) to remember what the previous value for display was used.

A boolean visible-type property would also help out reduce memory and CPU usage when horrible hacks such as "left: -999px;" are used. Even though elements don't appear on the screen or outside a layer's bounds, they will continue to be rendered and processed by the agent.

The more I think about it, the more I don't understand "display: none".

Stephanie Hobson said on August 03, 2011

An existing property that might help with some of the problems listed in the comments is -webkit-animation-fill-mode:forwards; It makes the styles in the last frame of the animation persist rather than reverting entirely to the class styles so this works to fade your element in from display:none:


@keyframes fade {
0% {opacity: 0; }
100% {opacity: 1; }
}

.thing{
display:none;
opacity:0;
}

.thing:target{
display:block;
-webkit-animation-name: fade;
-webkit-animation-duration: 1s;
-webkit-animation-fill-mode:forwards;
}

The thing that frustrates me about my above example is that it works as an animation but not as a transition applied specifically to the opacity property (-webkit-transition: opacity 1s linear;). Including one property that is not transitionable in either declaration causes the entire transition to fail, even though I don't want to transition it.

When I began experimenting with transitions I assumed when an element's stage changes all of the non-transitionable properties were changed instantly (essentially on frame 0) and then the transitions were applied (over the course of the specified duration). Not so. But this does appear to be the order of things if you are using an animation rather than a transition. WHY!??!?! I don't know and it makes my head hurt.

As for removing the document from the flow...we can't transition height or scale to remove the document from the flow either, we run into the same problems as display:none.

Additionally, height can't be transitioned from 0 to auto so if I need to element to fill it's natural space that's not an option.

Abhisek said on September 14, 2011

Great tricks. Thanks for sharing.

Ivan said on September 18, 2011

ok nice job man.

Ivan said on September 18, 2011

Nice post, I really like this post style.
Ivan @ souplantation coupons 2011

Adrian said on October 03, 2012

Create Animated Alert Box using MooTools and a lightweight (~4.5kb) Custom JavaScript Dialog Boxes. Welcome, Visitor. Subscribe to our RSS Feed and coniedsr adding this article/site to your favorite social bookmark site if you find it useful. Thank you! | Subscribe to RSS

Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.