W3C home > Mailing lists > Public > public-fx@w3.org > October to December 2015

Re: [web-animations] Freeing up forwards-filling Animation objects

From: Kevin Doughty <socallednonflipped@gmail.com>
Date: Sat, 21 Nov 2015 16:58:08 -0500
Message-ID: <CAAbwtRxF1VVaw_t9VWyYsVG_mLKi9nhPUAGkbO4bt73JxbYkjg@mail.gmail.com>
To: Brian Birtles <bbirtles@mozilla.com>
Cc: public-fx@w3.org, www-style@w3.org
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/occ/instm/CALayer/addAnimation:forKey
:

On Thu, Nov 19, 2015 at 2:08 AM, Brian Birtles <bbirtles@mozilla.com> wrote:

> 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 Saturday, 21 November 2015 21:58:42 UTC

This archive was generated by hypermail 2.3.1 : Saturday, 21 November 2015 21:58:43 UTC