- 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