0

In the following example, one animation will complete, and the other will keep going (because playState is not equal to finished):

const keyframes = new KeyframeEffect(null, [
    { // from
        opacity: 0,
    },
    { // to
        opacity: 1,
    }
], {duration: 2000, easing: 'ease'})

{
    const anim = new Animation(
        keyframes,
        document.timeline
    )

    anim.play()

    requestAnimationFrame(function loop() {
      console.log('1', keyframes.getComputedTiming().progress)
      if (anim.playState != 'finished') requestAnimationFrame(loop)
    }, 100)
}


{
    const anim = new Animation(
        keyframes,
        document.timeline
    )

    anim.play()

    requestAnimationFrame(function loop() {
      console.log('2', keyframes.getComputedTiming().progress)
      if (anim.playState != 'finished') requestAnimationFrame(loop)
    }, 100)
}

Why does one animation never get "finished"?

trusktr
  • 44,284
  • 53
  • 191
  • 263

1 Answers1

2

AnimationEffects, of which KeyframeEffect is a subclass, cannot be shared between Animations. This is because they rely on various state associated with their Animation.

(In particular, they need to know if they are associated with a transition / CSS animation in order to stack correctly, and they need to know their animation's playback rate in order produce the right value at the ends of their active interval.)

As a result, if you try to associate an AnimationEffect with an Animation, it is removed from any Animation it was previously associated with. This is specified in the procedure to set the associated effect of an animation.

Incidentally, this is why the KeyframeEffect interface has a "copy constructor" so that you can clone KeyframeEffects.

So in your example you could address this using:

const anim = new Animation(
  new KeyframeEffect(keyframes),
  document.timeline
)

Now, as for why one of the animations never finishes, that appears to be a bug in Chrome. In Firefox the first animation (which lost its AnimationEffect) will terminate almost immediately after starting because its associated effect end becomes zero.

brianskold
  • 809
  • 5
  • 10
  • Ah, nice, I didn't know about the copy constructor. That's useful. But this (in my opinion) shows a design that wasn't designed well enough, because the way it works allows users to pass references in a free way that doesn't work. Maybe instead the effect references should be created when an Animation is created (so effects are already associated, and there's no way to pass them to another animation), thus being a more coupled API. Otherwise this looser coupling leaves room for error or confusion. – trusktr May 23 '21 at 04:15
  • 1
    That's a fair point. The design comes about largely because Web Animations was initially specified to include nested effects but that was subsequently moved to level 2. Nested effects follow the model of DOM tree manipulation where adding a node as a child of another removes it from its previous parent. This same behavior was applied all the way to the root of the effect tree. – brianskold May 24 '21 at 05:28