CSS Transitions
Contents
CSS Transitions
CSS transitions are a CSS extension proposed by webkit to enable simple implicit transitions that animate CSS properties from one value to another.
Resources
Summary
The basic concept of CSS transitions is that you declare that certain CSS properties should be animated from the base value to the final value whenever they change. There are many different triggers that could cause the property to animate:
- a state change (e.g. :hover) causes the property value to change
- a user explicitly changes the style property via the DOM
- element.style.PropertyName=newvalue
- user applies a new 'class' attribute to the element that results in a different value for the css property
- this.setAttribute('class', "foo"))
- Modification of the actual style declaration
- document.styleSheets[0].cssRules[0].style.width=...
- Probably others
The common point in all of these examples is that any time the computed style for an element changes, we need to detect that and start an animation from the old value of that property to the new value of that property.
In addition, while an element is in the middle of a transition, querying the computed style for that property should return the intermediate animating value (i.e. the computed value should be animated, not just the presentation)
Requirements
Here's my attempt to map out a set of requirements to implement this feature in Mozilla:
- We need a way to detect when a style change occurs so that we can start an animation
- But we need to ensure that style changes caused by active animations don't trigger new animations.
- Computed style value must be updated during the animation
- After an animation has completed, the property value should remain at the final animation value
- If a new transition is started in the middle of an existing transition, it should use the current intermediate animating value as the starting point and transition to the new value (this is unspecified at the moment, but matches webkit's implementation)
Under-specified Behavior
Some things are not specified well in the CSS Transitions spec, so I've had to look at webkit's behavior for the intended behavior (or ask Dave Hyatt in some cases). The following are some of my findings:
- What happens to the underlying Style during a transition?
- The specification requires that the computed style should return the intermediate transitioning values, but doesn't specify what value should be returned for the element.style.foo property during a transition
- Testing on webkit reveals that this value is updated to the final value immediately, but it is masked by the animated value until the animation ends. It is not animated.
- David Baron: I'm not sure why this is an open question; this is the only possible choice. Transitions operate on computed values; element.style.foo, which reflects the style attribute, is only one of many possible specified values. Transitions happen whenever the computed value changes; this could be a result of a change in which selectors match or a change in the style attribute or a change in a style rule.
- How do you deal with subproperties that have different numbers of values?
- If the sub-properties are specified separately but there are unequal values specified, they should be handled in the same way that Multiple Backgrounds are handled in CSS3
- That spec doesn't really address how to handle values of 'inherit' in combination with the 'jagged array' issue, but webkit's implementation treats it as follows:
#parent{-webkit-transition-property: opacity, width;}
#child{-webkit-transition-property:inherit;-webkit-transition-duration:1s;}
will result in#child
having two transitions (opacity 1s, and width 1s). In other words, the number of values inherited from the parent is used in determining the total number of transitions.
- What happens when an element's -webkit-transition* properties are modified?
- If an element's -webkit-transition-* properties are changed (e.g. by dynamically changing the 'class' attribute such that a new set of style rules apply to it), the new style should be used to determine which properties should have active transitions (previous versions of webkit actually used the old style, but at some point the behavior was switched to use the new style's transition definitions)
- Do CSS Transitions apply to SVG content?
- dholbert asked about this since it could be quite complex to deal with interactions between SVG content that had both CSS Transitions and SMIL animations defined
- I saw some statements that implied that they should work, but when I tested CSS transitions with some SVG content on WebKit, I couldn't get it to work. I talked with Dave Hyatt, who said that it should work. But apparently only a subset of SVG CSS properties were supported (not the ones I was testing), so he added support for a few more properties while we talked.
- David Baron: We decided that CSS transitions should not be triggered by any change that is itself caused by SMIL animation. However, they should definitely apply to SVG in other situations.
- webkit does not support any shorthand properties for -webkit-transition-property
- David Baron: This would be pretty easy to support for us.
- When animating to or from 'auto' (e.g. for the 'width' property), webkit animates, but treats 'auto' as 0 (in other words, if you don't have a transition active for width, it would end up at a different value than if a transition is active for width. This is clearly a bug). When changing the property to a percentage (e.g. width:50%), webkit does not animate at all, it just updates the value immediately to the new value.
Comparison with SMIL animations
It would be very nice if we could share as much code as possible between the CSS Transitions feature and SMIL animation. dholbert already has a patch to animate CSS with SMIL described at SMIL:SMIL+CSS. Here I'll list a few thoughts about how SMIL animation and CSS transitions compare (disclaimer: I have only read through the SMIL animation spec quickly, so I might not have a full understanding of the details of SMIL animation)
- Animation triggers are significantly different between SMIL animation and CSS transitions
- SMIL animations are triggered by external events such as a mouse click or an elapsed time, etc.
- As mentioned above, CSS Transitions are implicit animations. The animation is only caused by an event indirectly insofar as that event causes the style property to change. (This adds a lot of complexity since the transition will need to animate the property from the old value to the new value, but we don't want those style changes to start a new animation).
- SMIL animation is not by default a permanent change. After a SMIL animation is finished, the animating value is removed and the base (original) value is allowed to 'show through' again. This is different than CSS Transitions since CSS Transitions is a simple animation from one base value to a new base value.
- I know that SMIL animations can 'freeze' the animation after it is finished, but this seem like a slightly different concept
- After a CSS Transition is finished, I think the final value should become the new base value, and the override style should be cleared. For a SMIL animation, a frozen animation would simply result in the override style value being maintained indefinitely rather than changing the base value
- I know that SMIL animations can 'freeze' the animation after it is finished, but this seem like a slightly different concept
- It seems that -webkit-transition-function could be mapped fairly easily into SMIL animation's calcMode with values of 'linear' and 'spline'
- We would just have to map the non-linear CSS keywords (e.g. ease-out, ease-in, etc) to a set of cubic bezier control points.
- Both specs treat a negative start time (called 'delay' in CSS transitions) in the same way (e.g. start the animation immediately but act as if you had started in the past)
- SMIL transitions are always 1:1. In other words, a given animation element specifies a single target element and attribute to be animated. By contrast, a CSS transition can specify that it applies to a property of 'all' (which means that any style property change will be animated), and a given transition definition can apply to any (or all) elements of a document (based on what elements are matched by the style selector)
- Because of this, it's not feasible for CSS animations to be treated the same way that SMIL animations are treated (e.g. all animation definitions are added to a list of animations when a document is loaded/parsed). If we did that for CSS transitions, we could end up with thousands of elements in the animation list, and most of them would probably never be used.
- The approach we'll most likely need to use for CSS transitions is to detect a change in a property that has a transition applied to it. At that point, we can dynamically create an animation that maps to a single element and property, and add it to the animation list. Then when the animation is finished, we should remove it from the animation list.
Differences in animation specifications
SMIL animation for CSS properties is mostly specified with pre-parsed values.
- e.g. <animation from="100px" to="200px" .../>
A simplified overview the current code flow for SMIL/CSS is something like this (for a from-to animation):
- parse a string representing the 'from' value into a nsCSSValue and stuff it into a nsSMILValue
- parse a string representing the 'to' value into a nsCSSValue and stuff it into a nsSMILValue
- interpolate the current position between the from and to values and store it as a new nsCSSValue inside a nsSMILValue
- requires some sort of general nsCSSValue interpolation framework (between different units, different types of units, etc)
- Set the interpolated value as the 'override style' value
- This essentially requires us to convert the nsCSSValue back to a string and call nsICSSDeclaration->SetPropertyValue(propId, string), which then re-parses the string into a nsCSSValue, etc...
On the other hand, for CSS Transitions, the animation is only initiated if the computed style of an element changes. In this case, the animation is between the previous computed style and the new computed style. So if we were going to use the existing SMIL/CSS framework to do the animation (and I do have a prototype which does this), I would need some general way to convert an arbitrary property's computed style value into a string (or we could add new API that would allow me to bypass the string portion and just convert directly from computed style to nsCSSValue/nsSMILValue, in which case we would need a general way to convert from a computed style to a nsCSSValue), and then we would follow the same code flow as described above.
- However, even if such a general facility existed to convert from computed style back into an nsCSSValue, it seems like we would be doing quite a bit of extra work parsing, reparsing, converting, etc.
- in the CSS transition case, it would be nice if we could interpolate the computed styles directly and then somehow set that computed style more directly rather than going through all of the intermediate parsing / conversion
Interaction with SMIL Animations in SVG content
- In WebKit, CSS Transitions can apply to SVG content. This raises the question of how transitions and SVG animations should stack when they're both trying to animate the same property.
- Currently, the proposed CSS Transitions spec doesn't address this at all.
- It seems like it'd make the most sense to treat the transition as just another non-additive animation in the SMIL Sandwich Model. To elaborate, here are some relevant quotes from that model's description:
- "Each additive animation adds its effect to the result of all sandwich layers below. A non-additive animation simply overrides the result of all lower sandwich layers."
- "The animation first begun has lowest priority and the most recently begun animation has highest priority. When two animations start at the same moment in time, the activation order is resolved as follows: [...]")
Notes from 2008-10-30 Meeting
- Decisions:
- SMIL-Animated style should be stored in a special per-element rule node, which will be kept up-to-date by the SMIL/animation code.
- This rule node fits right in the middle of the CSS cascade (overrides other style, but not
!important
styles) - We'll detect style-changes that require transitions inside of
DidSetStyleContext()
-
DidSetStyleContext()
will need to know whether it was called due to a "real" style change, or due to a style change from a transition/animation. This will hopefully prevent transitions/animations from triggering new transitions each time they update the style. :) - Initially, we won't support transitions on pseudo-elements (e.g. "first-letter")
- UPDATE: SMIL and CSS Transitions need to insert at different points in the cascade, because:
- SMIL's animated style needs to be inherited by its children (e.g. animating font-size on the document)
- CSS Transitions' animated style should never inherit, because it's inherently a per-element animation, on changes to that element's computed style
- David Baron: I don't think this is actually true.