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

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

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>
Message-ID: <564D7562.2030104@mozilla.com>
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

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 19:49:54 UTC