- From: Brian Birtles <bbirtles@mozilla.com>
- Date: Thu, 19 Nov 2015 16:08:18 +0900
- To: "public-fx@w3.org" <public-fx@w3.org>
- Cc: "www-style@w3.org" <www-style@w3.org>
Hi, We have a difficulty in Web Animations with regards to object lifetimes for animations that fill forwards. The trouble is one can do the following: elem.addEventListener('click', evt => { evt.target.animate({ opacity: [ 0, 1 ] }, { duration: 500, fill: 'forwards' }); }); What this does, is create a new animation that fills forwards, on every click. Now, if we call: elem.getAnimations(); It should return *all* the animations we've generated. There are few reasons for that: a. You should be able to use getAnimations() to help answer the question, "Why does my element have this style value?" i.e. This method needs to return any animation that is affecting the element in question. b. Animations can add together so you need to keep track of all the past animations anyway so you get the right final result. e.g. you can do the following // Nudge right elem.addEventListener('click', evt => { evt.target.animate({ transform: 'translate(50px)', composite: 'add' }, { duration: 500, fill: 'forwards' }); }); The total distance the element is translated is the sum of the fill values of all animations that have run up to that point. All that means you really need to keep track of all the forwards-filling animations. Forever. Why is this a new problem? For CSS transitions and CSS animations, once animations are no longer referenced by specified style they are cancelled and no longer affect the element. You just don't accidentally end up in a situation with millions of old animations lying around. To do that, you'd have to have a *lot* of elements or have an animation-name value that is very long. Either case tells you to expect things to go slowly and are somewhat bounded. With the animation API, however, as shown in the examples above, it's quite easy to get into a situation where the UA has to keep a lot of Animations around without the author realizing it. Solutions we've discussed until now: a. Automatically cancel any filling animation when other filling animations cover the same properties. => Doesn't work for addition. b. Make getAnimations() only return the *last* animation that is filling per-property. => For the addition case, you'd still need to keep around some sort of summary of the result of previous animations. Now, supposing you don't expose that summary, the view you see from the API is inconsistent. c. Expose the summary view of underlying filling animations as some sort of special animation or a special attribute. => This is awkward, but maybe worth revisiting? e.g. elem.getFillStyle().opacity? d. "That's the author's problem" or "Just let UAs just do whatever they feel like" => This is our current position and it's not great. I really think we need to fix this. I don't have any brilliant ideas. Revisiting (c) could work, or perhaps some sort of "animation collapsing"? The rough idea of "animation collapsing" is that when the UA has two or more filling animations on the same properties on the same element, it basically takes the fill values of the underlying animations and squashes them into the property values specified on the top-most filling animation. Then the underlying animation ends becoming empty and can be discarded. (I'd really like to make it so that the underlying animation actually gets cancelled and you get an event for it, but I think when it comes to group effects, you want to be able to collapse individual effects without having to have a complete match for all effects.) Adding a bit more details I think it could be something like: Assume: * all finished animations do NOT fill forwards have been discarded * Element.getAnimations() does not return Animations targetting Element if the set of keyframes is empty For any two effects, A and B, (with corresponding animations, 'Animation A', and 'Animation B') where: a. A and B have the same non-null target element b. Animation A and Animation B are both finished c. Animation A and Animation B each have a fill mode of either 'forwards' or 'both' d. Animation A and Animation B have the same animation type e. The common animation type of Animation A and Animation B allows collapsing (disallow for CSS animations/transitions) f. For each animation property that is common to both A and B, there is no animation effect referring to that animation property whose corresponding animation has a composite order between that of A and B. Perform the following steps: 1. Assume that A refers to the effect whose corresponding animation has a *lower* composite order of the two. 2. For each animation property, |property|, that is referenced by both A and B, update each specified value |property| on B as follows: i. Let |b| be the specified value of |property| on a keyframe in B. ii. Let |a| be the fill value of |property| on a keyframe in A. iii. If the composite mode associated with |b| is 'replace', let b' = |b| and composite' = 'replace'. Otherwise, if the composite mode associated with |b| is 'add', let b' = add(|a|, b) and composite' = the composite mode of |a|. Otherwise, (the composite mode associated with |b| is 'acccumulate), let b' = accumulate(|a|, b) and composite' = the composite mode of |a|. iv. Update |b| with b' and use composite' as the composite mode for the keyframe. (TODO: Add logic here to handle when we have multiple properties on the same keyframe and we are changing the composite mode. Basically, make a new keyframe in that case, but then combine such keyframes at the end of the process.) 3. Remove all values of |property| on keyframes in A. 4. Remove any empty keyframes on A. It's pretty complicated and I don't love it, but I think it's a little closer to the pit of success we'd like to create? If nothing else, it might spark a better idea from someone else. Best regards, Brian
Received on Thursday, 19 November 2015 07:08:50 UTC