- 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:53 UTC