[web-animations] Web Animations minutes, 12 Mar 2014

Web Animations minutes, 12 Mar 2014

Present: Doug, Mike, Shane, Brian
Etherpad: https://etherpad.mozilla.org/ep/pad/view/ro.n4i87odhZVn/latest

Agenda:

1. Events
2. Next WD
3. Staged API release
4. Accumulate
5. Custom Effects
6. CSS Mapping


1. EVENTS
=========

Background research: http://jsfiddle.net/6SudQ/1/
Is it ok to drop events? Firefox, Chrome, Safari: Do (for CSS 
Animations), IE: Doesn't
Developing for mobile is harder if events are dropped. On the other 
hand, forced dispatch of events is bad for mobile too.

CSS Event usage stats from Chrome:
   Start 0.06% 
http://www.chromestatus.com/metrics/feature/timeline/popularity/131 (600 
ppm)
   Iteration 0.002% 
http://www.chromestatus.com/metrics/feature/timeline/popularity/134 (20 ppm)
   End 0.37% 
http://www.chromestatus.com/metrics/feature/timeline/popularity/128 
(3700 ppm)

Proposal: the only event we provide is onfinish() event on a Player. 
That's it. Nothing on a TimedItem. We may relax this slightly in future 
iterations of the API - but only to the extent of adding more 
Player-level events.

Shane: Events are not well-suited to animation scheduling
They are mainly useful for clean-up of resources

Use Case 1: Stop at the end of the current iteration

Currently people would do something like:

animation.oniteration = function() {
     animation.player.cancel();
}

But you can instead do:

animation.specified.iterations = animation.currentIterations + 1;

Advantages:
- guaranteed accuracy (even on mobile devices), no details of event 
firing model necessary

Disadvantages:
- not idiomatic

Use Case 1b: Stop at the end of the current iteration and clean up a 
resource.

Now:

animation.oniteration = function() {
     animation.player.cancel();
     resource.cleanMeUp();
}

Instead:

animation.specified.iterations = animation.currentIterations + 1;
// note that this can be set up in advance, and is not logically tied to 
iteration end
animation.player.onfinish = resource.cleanMeUp.bind(resource);


Some concern about whether we have addressed sufficient use cases with 
just onfinish. Proposal to make custom effects more suitable for 
addressing trigger-style use cases where the author wants to be able to 
trigger e.g. an XHR request etc. (And absolutely require this to be 
aligned with specific features of their animation.)

Suggestion: What if we made some guarantees about when custom effects 
are called. For example, if we said they get called once for each sample 
where they were active in the interim period, then you could use a 
zero-length custom effect to trigger the action.

The question is whether this has better performance characteristics than 
events in general. We think it does but need to verify this.

We note that we also probably need to guarantee that a custom effect 
gets at least one call *after* its interval is finished (i.e. with a 
null time fraction) so it can do clean up, anyway.

Actually, simply guaranteeing this would be sufficient to satisfy the 
above requirement about one call per sample where the custom effect is 
active.

Do we provide any guarantees about the order in which custom effects are 
called? Based on when they start? Or simply "document order"?
Suggestion: Simply use the order we already apply for calling effect 
callbacks (i.e. player sequence, tree order etc.)



Side issue: Element.animate returning an AnimationPlayer?

It seems like returning an AnimationPlayer could be nice:

e.g.

   elem.animate(...).pause();
   var player = elem.animate(...);
   player.pause();
   ...
   player.play();

and likewise:

   elem.animate(...).onfinish = function() {...};

One property of the current setup (where we return an Animation object) 
is you can do this:

    new AnimationSequence([
         elemA.animate(...),
         elemB.animate(...),
         new Animation(elemC, ...)]);

We *could* support this even when returning AnimationPlayer by making 
the ctor of AnimationSequence take a union type (Animation or 
AnimationPlayer) and automatically take the source content of any player.

But the problem is if you do:

   var a = elemA.animate();
   a.onfinish = function() {};
   var group = new AnimationSequence([a]);
   document.timeline.play(group).onfinish = function() { ... };
   // Why doesn't a's onfinish play anymore? Where did it go?

We currently have a similar problem, although not quite as subtle:

   var a = elemA.animate();
   a.player.onfinish = ...;

also is it horrible to have to write

new AnimationSequence([
       elemA.animate(...).source,
       elemB.animate(...).source,
       new Animation(elemC, ...)]);

What about other syntax?

new AnimationSequence([
       [elemA, ...],
       elemB.animate(...).source,
       new Animation(elemC, ...),
       elemE.animate(...),
       elemF.somethingElse(...)

What if accepting a player as an arg to AnimationSequence or 
AnimationGroup caused any onfinish handlers to be promoted to the group? 
Anybody? No?

What about throwing an exception if you try to add a player to a group 
that has an onfinish event handler?

Mike: I prefer having to type elemA.animate(...).source. It makes it 
explicit, and removes the source of surprise.

Note that it's not only onfinish, but also playbackRate etc. that would 
be lost by extracting the source from its player silently.

Also, allowing:

   new AnimationGroup([ elem.animate(...) ]);

has the other surprising effect of causing the animation to stop. 
Requiring the author to type 'source' makes this less surprising.


** Resolutions:
  - only provide 'onfinish()' on Player. No other events
  - Custom effect callbacks must receive one callback after its interval 
has finished
  - Element.animate() returns an AnimationPlayer. The player will auto-play.
  - Do not accept adding an AnimationPlayer into an AnimationSequence. 
Developer must use .source explicitly.


Side issue: Discussed Mike Swanson's feedback about the cycle Player -> 
TimedItem -> Player.

Is it simpler if we have a model where you can't go from Animation to 
Player? Where Players are the fundamental building blocks you deal with?

If we removed TimedItem.player you would be unable to navigate back to a 
Player but that would force authors to think of players as the primary 
units and hold references to them.

At the same time we would probably remove getCurrentAnimations since it 
seems odd to be able to get animations individually and have no way to 
perform play control on them.

** Resolutions:
  - Mark 'getCurrentAnimations' and TimedItem.player as at risk. After 
we have more content available we can determine if these are really needed.


2. NEXT WD
==========

No real timing restrictions. Just need to make a few changes.

Will try to get event changes in.


3. STAGED API RELEASE
=====================

We've talked previously about having stages, where we agree on what will 
be part of stage N, and we wait until there are two in-the-wild impls of 
stage N before going on to stage N+1.

Level of confidence in proceeding with a stage should be 'we're pretty 
sure this won't change', not 'we guarantee this won't change'.

Discussion about whether we target on exposing the API for the purpose of:
  * Creating animations from script
  * Querying animations created with CSS

What are the bits we are considering initially?

  * element.animate
  * Animation
  * KeyframeEffect
  * timeline.getCurrentPlayers
  * element.getCurrentPlayers
  * Timing
  * AnimationPlayer
  * AnimationTimeline
  * document.timeline

Questions that need to be resolved:
Does getAnimationPlayers return the set of CSS players too?

Some possibilities:
A) The above set
B) Don't even expose Animation/TimedItem, just AnimationPlayers (with no 
source) member:
   -- that lets you create animations from script
   -- you can also pause animations and seek them

Some discussion about whether getAnimationPlayers returns CSS Animations 
and Transitions.

- We can try exposing CSS Animations for now. Possibly the players can 
be manipulated but the sources would be read-only
    Then, if you want to modify the source, you simply clone it and put 
the clone in: that makes the relationship between the generated 
Animation and the CSS markup clear--you clearly break the connection 
when you do it so there are no (less) surprising results
    Need to work out how animation-play-state interacts with pause 
control on the player (see below)

- CSS Transitions are less clear. It seems exposing CSS Transitions 
would be useful for cross-browser testing and polyfilling behaviour but 
there is some uncertainty about the interaction with the transition.
   One possibility raised was about making Player.source immutable for 
transitions although it's not clear if we want to do this.

- We don't want to expose getAnimationPlayers without resolving this 
since it will break content if we later expand the scope of what gets 
returned.

Possible approach:
Feature set A: Element.animate
Feature set B: Element.getAnimationPlayers

These can be worked on in parallel and release first whatever is most 
stable.

Resolutions:
  * change getCurrentPlayers to getAnimationPlayers
  * Get today's changes into the spec and perform a thorough review to 
identify remaining blockers for stable release of A and/or B
  * If spec for A and/or B is still not stabilised in the next month, 
consider paring them down even further to a stable subset (eg only 
include 'cancel()' on the Player from element.animate).


4. ACCUMULATE
=============

Shane: we should regularise accumulate so that:

(1) accumulating iterating animations can be 'broken out' as an 
AnimationSequence of simple Animations (all other iterating animations 
have this property)

(2) we can support the GSAP use-case of independently animatable 
transform 'channels'
Proposal: add 'accumulate' to composite operators. Add iteration-fill to 
timing.

(3) it means there is a defined behaviour for accumulating animations 
that have both replace and add keyframes.
BUT: this can easily cause infinitely expanding value strings, for example:
new Animation([{transform: 'translate(100px) rotate(0deg)'}, {transform: 
'translate(200px) rotate(10deg)'}], { iteration-fill: 'forwards', 
iterations: Infinity, duration: 0.2 });

Another option is to just add accumulate as a compositing operation, and 
either keep the accumulate flag on animations or replace it with 
'iteration-composite: replace | accumulate'. This preserves benefits 
(1-3) above.

Working through some examples to see how this should be specced:

   Underlying value: 150px;
   Endpoint 1: Replace: 100px;
   Endpoint 2: Add: 100px;

   Iteration 1:
   End points 100px, 250px (start 100px, gradient 150px)
   At 50%, 175px

   Iteration 2:
   Method A: Accumulate end points then interpolate
   End points: 100px + 250px = 350px, 350px
   At 50%, 350px

   Method B: Interpolate then accumulate
   End points 100px, 250px
   50% before accumulating 175px
   Accumulate: 425px (end points: 350px -> 500px)

   ---

   Method B with replace/replace
   Replace: 100px -> Replace: 200px

   Iteration 1
   100px -> 200px
   50%: 150px

   Iteration 2
   300px -> 400px
   50% 350px

   ---

   Method B with add/add
   Add: rotate(0deg) -> Add: rotate(30deg)
   Underlying value: translate(100px, 100px)

   Iteration 1
   translate(100px, 100px) rotate(0deg) -> translate(100px, 100px) 
rotate(30deg)

   Iteration 2
   translate(100px, 100px) rotate(30deg) -> translate(100px, 100px) 
rotate(60deg)

Summary:
Add 'accumulate' as a compositing mode. Add 'iteration-composite' as a 
property of Effect. It defaults to 'replace', but when set to 
'accumulate', then for animations with multiple iterations:
(1) keyframes are resolved against the underlying value
(2) the current value is interpolated from these resolved keyframes
(3) once past the first iteration, this current value is accumulated 
onto the final value of the previous iteration (it is used directly for 
the first iteration)

Actions:
Shane to check we can support SVG with this scheme
Shane to draft some spec text


5. CUSTOM EFFECTS
=================

Earlier we decided:
Custom effects must guarantee at least one sample after the end of the 
effect, in either direction.

Some questions that Doug, Shane & Mike wanted to ask:
(1) are zero-length custom effects guaranteed to be called?
It looks like the answer is yes, as a consequence of the post-effect-end 
guarantee

(2) are custom effects called every sample, or just when the time 
fraction changes?
Just when the time fraction changes,

(3) are custom effects called multiple times when filling?
No, just once

The general rule is we call the effect callback if and only if one or 
more of the following is true:
* The time fraction changed
* The current iteration changed
* The phase changed <-- this part is not right since we can be in the 
null phase on both sides
* An ancestor's current iteration changed?

We might try a top-down approach to defining this, but this is roughly 
what we are looking for


6. CSS MAPPING
==============

1. @keyframes map

Proposal from Tab:
CSS-defined @keyframes end up in a map indexed by name.
(1) these can be read out of the map and used in other animations
(2) these are read-only and can't be modified (but can be cloned)
(3) new entries can be added to the map, and these can be referenced 
from CSS (but will not generate CSS @keyframes rules)
(4) existing entries can be replaced, which will update future 
animations referencing the replaced name. Existing animations will 
continue to use the old KeyframeEffect. Changes to the CSS will not be 
reflected in the map for this name from this point.

This seems to be a nice-to-have, but isn't necessary in V0 of the API.

It seems like this should be a map from String to AnimationEffect rather 
than to KeyframeEffect.

(Side note: this idea of a general repository of animation effects keyed 
by name could possibly be used in the future to allow CSS to trigger a 
motion path effect defined by script/SVG)

But...
@keyframes anim {
     100% { foo: bar }
}

Is not the same as
[{foo: bar, offset: 1}]

You need to snapshot a CSS Animation before generating a 
KeyframeEffectModel. What we can do is just make this a property of CSS 
Animation generation. Alternatively we could ask the CSSWG what they 
think about changing this property of CSS Animations.

Some investigation in snapshotting in CSS: from 
https://code.google.com/p/chromium/issues/detail?id=345880
   http://jsfiddle.net/9CWAB/9/
   http://jsfiddle.net/3scz4/1/

It seems Firefox does not snapshot (or at least it regenerates snapshot 
values when the underlying value changes)

 > Brian to check with David if this is intended behaviour or an 
unintended side-effect

2. How are CSS Animations exposed?

 From the conversation above (item 3), we want 
element.getAnimationPlayers and document.timeline.getAnimationPlayers to 
include a player for each running CSS Animation (on the element / in the 
document).

The source will point to a read-only TimedItem.
The playbackRate, currentTime, startTime and play control methods will 
be live and work as normal.

Q1: Where should we map animation-play-state to?
Q2: Should the source attribute be immutable?

Proposal:

CSS -> WA
Setting animation-play-state just triggers the procedure under "updating 
the paused state"

WA -> CSS
Calling pause() and querying computed style of animation-play-state: 
returns "paused"?
    Then how can you unpause it from CSS? If you set 
animation-play-state: running you're setting it to the same value so it 
will be ignored?
    What if we updated the inline style with the animation-play-state?

What happens if I do:
#foo {
   animation: thing1 2s;
}

var p = foo.getAnimationPlayers()[0];
p.source = p.source.clone();

foo.style.animationName = 'thing2';

 > It gets cancelled

We have two candidate models:

A) Single pause source. Changes in CSS/model reflect in the other. 
Unpause in either places unpauses.
B) Two pause sources: CSS and model/API. *Both* have to be unpaused in 
order for it to run.

 > Shane to enquire about how bad an idea it is to overwrite the inline 
style. If this is ok, A seems preferable.

We do *not* want the source attribute to be immutable.


3. How are CSS Transitions exposed?

It would be nice for CSS Transitions to be aligned with CSS Animations. 
Mutable sources let you do weird things:

   #foo {
       transition: left 4s;
   }

   foo.style.left = '100px'; // triggers a transition
   var p = foo.getAnimationPlayers()[0];
   p.source = new Animation({backgroundColor: 'green'}, 40);
   // now left is 100px, but backgroundColor is changing to green 
veerrrrrry slowly
   setTimeout(function() {
       p.style.left = '200px';
   }, 2000);
   // 2 seconds later, left is changed again, triggering another 4 
second transition from left: 100px to left 200px. The backgroundColor 
change is cancelled.

This gets potentially even stranger if the new source actually touches left:

   foo.style.left = '100px'; // triggers a transition
   var p = foo.getAnimationPlayers()[0];
   p.source = new Animation({left: '70px'}, 40);
   // now left is animating to 70px instead of 100px, and over 40s 
instead of 4s. If it finishes naturally, left will snap to 100px at the end.
   setTimeout(function() {
       p.style.left = '200px';
   }, 2000);
   // This should still force a new transition to be generated, and the 
old one should be cancelled. What should the 'from' value be in this case?

But it's probably OK.

Reversing?

The reversing behaviour defined in CSS Transitions is complex and is 
best achieved by producing a new Animation to represent the reverse 
transition (rather than, say, calling reverse() on the player).
Does it create a new player too? Yes.

If you substitute a transition's source, it should still behave like a 
transition wrt to stacking order etc. That means we'll need to update 
the 'custom animation priority' so it is attached to a player rather 
than an animation effect.

Topics for tomorrow:

* timeLag
* liveness
* supports
* easings on keyframes
* TimedItemList
* test suite
* shared model implementation
    - sounds like Blink and Gecko's impl approaches will be sufficiently 
different that this won't be practical.

Received on Wednesday, 12 March 2014 09:19:03 UTC