Platform/Layout/Web Animations

From MozillaWiki
Jump to: navigation, search

Overview

Feature comparison between CSS, SVG, and Web Animations

CSS-SVG-Web-Animations.png

We could also add "Synchronizing with media" to Web Animations but I (Brian) have some uncertainty about if that will make it into v1.

Simplified class diagram of API

Web animations simplified class diagram.png

Some things have been simplified:

  • Event classes etc. not shown
  • Data types not shown
  • Keyframe related dictionaries not show
  • Timing dictionary not shown
  • Enums not shown
  • Extensions to Document, Element not shown

Spec issues

Spec link: https://dvcs.w3.org/hg/FXTF/raw-file/default/web-anim/index.html

Issue 1: Timing interface

Currently we have this kind of arrangement:

interface TimedItem : EventTarget {
   // Specified timing
   readonly attribute Timing specified;
  
   // Calculated timing
   readonly attribute double              startTime;
   readonly attribute unrestricted double iterationDuration;
   readonly attribute unrestricted double activeDuration;
   readonly attribute unrestricted double endTime;
};

interface Timing {
   attribute double                             startDelay;
   attribute FillMode                           fillMode;
   attribute double                             iterationStart;
   attribute unrestricted double                iterationCount;
   attribute (unrestricted double or DOMString) iterationDuration;
   attribute (unrestricted double or DOMString) activeDuration;
   attribute double                             playbackRate;
   attribute PlaybackDirection                  direction;
   attribute DOMString?                         timingFunction;
};

Why do we have iterationDuration (and activeDuration) twice? Because iterationDuration can be "auto" which is especially useful for timing groups where it means "stretch to fit the children". At the same time it's useful to be able to query the current computed value.

So you have:

 anim.specified.iterationDuration = "auto";
 var actual = anim.iterationDuration;

I'm a bit concerned about setting and getting in different places. Not sure if the 'specified' is weird.

One alternative:

interface TimedItem : EventTarget {
   // Timing
   attribute double   startDelay;
   attribute FillMode fillMode;
   attribute Duration iterationDuration;
   attribute Duration activeDuration;
   attribute double   playbackRate;
   // ...
   
   // Scheduled time
   readonly attribute double              startTime;
   readonly attribute unrestricted double endTime;
};

interface Duration {
  double    sec;
  DOMString string;
}

Usage:

 var specifiedDur = anim.iterationDuration.string; // "auto"
 var computedDur = anim.iterationDuration.sec; // 5
 
 // Update duration to 3s
 anim.iterationDuration.sec = 3;
 // anim.iterationDuration.string -> "3s"
 
 // Update duration to 3s (alt.)
 anim.iterationDuration.string = "3s";
 // anim.iterationDuration.sec -> 3
 
 // Reset to auto
 anim.iterationDuration.string = "auto";  
 // anim.iterationDuration.sec -> 5

Google have expressed a (fairly strong) preference for keeping specified and computed timing separate.

Thoughts? Suggestions?

Issue 2: Timing function list syntax

You can't do bounce functions in either CSS or SVG. These are really popular; most animations APIs have heaps of timing functions like this.

I wanted to extend cubic-bezier functions to take a series of numbers that define additional cubic bezier segments. The idea was to be a common-denominator so authoring tools could convert complex curves to this format. Then, in a future version we'd add syntax for generating spring functions etc.

Google don't like the long list of numbers. So they have proposed chaining timing functions like so:

We had another think about how best to represent chained timing functions and
still prefer the idea of splitting the input time fraction into ranges, and
specifying a timing function to cover that range.

"ease-in 0.4 0.4, cubic-bezier(1, 0, 1, 0) 0.7 0.7, ease-out"

This has several nice properties ...

- It allows chaining of arbitrary types of timing function
- It allows specification of discontinuous timing functions
- Each timing function can be parameterised independently of the range it
  spans. For example, in the above, the arguments to cubic-bezier() specify the
  shape of the curve, which is unchanged if the size or position of its range
  is changed.
- It matches the keySplines / keyTimes feature of SVG exactly

To simplify the common-case of chained beziers where c1 continuity is required,
we propose adding a 'smooth' option. Specifying this option means that the
direction of the vector representing the starting control point of the bezier
is ignored, and only its magnitude is used. Instead its direction is set to be
equal to that of the tangent at the end of the previous segment (ie the
negative of the vector representing the end control point in the case of
a bezier). If the smooth option is specified on the first segement, a direction
of (1, 0) (i.e. horizontal) is used. For example ...

"ease-in 0.4 0.4, smooth cubic-bezier(1, 0, 1, 0) 0.7 0.7, smooth cubic-bezier(0, 1, 0, 1)"

Here the direction of the start control point of the first bezier is ignored,
and set to match the end of the ease-in segment. Similarly, the direction of
the start control point of the second bezier is ignored, and set to match the
end control point of the first bezier.

As a further simplification, we propose that if the coordinates specifying the
end of the range are ommitted for some segment, the ranges of any
underspecified segments are set so as to evenly distribute the available space
(both in x and y). For example ...

 "ease-in 0.2 0.2, ease-in, ease-in"

is equivalent to ...

"ease-in 0.2 0.2, ease-in 0.6 0.6, ease-in"

Furthermore, if only one such coordinate is omitted, it is used for both the
x and y components. For example ...

 "ease-in 0.2, ease-in, ease-in"

is equivalent to ...

 "ease-in 0.2 0.2, ease-in 0.6 0.6, ease-in" 

I'm a bit concerned about the complexity of the numbers after the timing function. It's not obvious what they are and it's an additional concept, "rendering the timing function into a window". However, we need something like this not only for bounce effects but also because we removed timing functions from key frames.

Issue 3: play() and playNow()

CSS doesn't really define when animations start. That can be problematic but it can also be helpful: e.g. allows delaying animation to compensate for vsync etc.

Proposal:

interface Timeline {
  Player playNow(optional TimedItem? source = null);
  Future play(optional TimedItem? source = null);
  ...
}

playNow creates a new Player with a startTime set to the current time of the Timeline.

play creates schedules a new Player to be created and run. The UA starts it at the earliest possible time ensuring that it begins from the first frame. When it is started, the startTime is filled in and the Future is resolved (passing in the Player as the argument).

Issue 4: Out of range keyframe offsets

We currently don't allow positioning keyframes outside of the range [0,1] (i.e. 0% -> 100%). Why would you ever go outside this range? Because in CSS timing functions let you overshoot/undershoot. In these cases you extrapolate the values as needed.

Google want to allow you to specify keyframes outside this range so that when you overshoot you can control the animation beyond just simple extrapolation of the last segment (although you can already create a zero-length segment at 0 or 1 to at least control the slope of the values outside the [0,1] range).

Doing so has an impact on what you do when there is no keyframe at 0 or 1.

Currently, if there is no keyframe at 0 or 1, we create a new keyframe at these positions that represents the underlying value. This is similar but different to what CSS does (it snapshots the value when it creates the additional keyframe so its not quite the same.) This is important for the (very common) case of "to-animation", i.e. where you only specify one value and you want to animate from the current value to the target value.

If we allow keyframes outside [0,1] then it no longer makes sense to fill in keyframes at 0 and 1 (since you may have a keyframe, for example, at -0.3). The proposal from Google is you only fill these in if you have one value specified (i.e. to-animation or from-animation), otherwise you just extrapolate from the segments there.

So we're basically choosing between allowing keyframes outside [0,1] or aligning with CSS. I'm not sure if the special behaviour for a single-keyframe animation is inconsistent.

In terms of use cases there may be a use case for specifying values outside [0,1] range regarding calculating the range of overshoot but I'm not sure (i.e. specify a frame at 1.2, with a value of 800px, then using a timing function that overshoots to 1.2 you know the object will only go as far as 800px--previously you'd have to specify the appropriate value at 1.0 and do the extrapolation calculation yourself; however, calculating the maximum value of the timing function is still non-trivial with the current syntax.)

Issue 5: The KeyframeShortcut type

When creating an animation we want it to be as simple as possible, particularly we have this syntax:

 // Animate the 'left' property over 3s to 100px
 elem.animate({ left: '100px' }, 3);

But putting left on the LHS like that isn't possible in WebIDL unless we enumerate every animatable property in a dictionary and give them a default value of undefined.

I guess the WebIDL-native way would look like this:

 elem.animate({ property: 'left', value: '100px }, 3);

That's not too bad but then when you animate multiple properties it gets really complex, like:

 elem.animate([ { values: [ { property: 'left', value: '50px' }, { property: 'top', value: '50px' } ] },
                { values: [ { property: 'left', value: '100px' }, { property: 'top', value: '100px' } ] }] );

So we defined a KeyframeShortcut type to support { left: '100px' } etc. It's ECMAScript-only and we defined the procedure for interpreting the object so you don't get inconsistent results due to the order in which properties are read.

In addition we have a regular Keyframe dictionary and KeyframeValue dictionary which corresponds to the more complex syntax above. Google would like to get rid of the dictionaries and just keep KeyframeShortcut. That would mean there's no interface for non ECMAScript contexts. It also means iterating over the values of a keyframe returned by the API is somewhat more complex: you have to iterate over the properties of the object and be careful to ignore any 'offset' and 'composite' properties as opposed to simply iterating over the 'values' array.

I'm not sure how much all this matters?

Minutes from 2013-05-21

  • Some concern expressed over complexity of model
    • In particular, the Player concept seems confusing. Possible preference for play/pause directly on animate? Or removing the timeline?
    • Possible interest in identifying a subset for implementation
  • Some issues identified:
    • Need extra hooks into the prioritization of animations to control where, for example, transitions apply
    • Need means to start animations before document load (CSS Transitions/Animations do this despite wording in early drafts)
    • Need to define what happens when a player refers to the document timeline of one document but its animations refer to elements in another document
    • Not sure if Player.startTime needs to be writeable
  • Brief discussion of listed issues suggests:
    • Issue 2: Some concern over the numbers using in proposed timing function list syntax.
    • Issue 3: play()/playNow() may be ok. Use of Futures seems a bit weird.
    • Issue 5: KeyframeShortcut only is probably fine.


Follow-up meeting: Thurs 2013-05-23 2pm Taipei time (0600 UTC)

Agenda for next meeting:

  • If we were to implement it, how could we approach it?
    • Is there a subset we could focus on? Can we implement without the API? etc. etc.
  • Re-visiting the issues listed above that we haven't yet discussed

Implementation

Tracking bug: bug 875219

Large pieces:

  • Script API
  • Rework CSS in terms of same base objects
  • Rework SVG in terms of same base objects