- From: Brian Birtles <bbirtles@mozilla.com>
- Date: Wed, 12 Mar 2014 18:18:31 +0900
- To: "public-fx@w3.org" <public-fx@w3.org>
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