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

Reply via email to