web animations api, resetting 1st animation at end of chain

298 Views Asked by At

I'm experimenting with the web animations API for the first time, and trying to chain some values together. I'm a little confused as for this simple animation, where it will translate 200px along the X axis, then 200px down along the Y axis once the first animation is complete.

However, at the end of the animation, it resets the X value of the initial animation. I've tried playing around with 'both' | 'forwards' for the fill mode property, as well as setting translateX to 0/200 in the second transform declaration. Feel like I'm missing something quite simple here.

const box = document.getElementById('box');
const animateRight = box.animate(
    [{ transform: 'translateX(0)' }, { transform: 'translateX(200px)' }],
    {
        fill: 'forwards',
        easing: 'ease-in-out',
        duration: 2000,
    }
);
animateRight.finished.then(() => {
    box.animate([{ transform: 'translateY(0)' }, { transform: 'translateY(100px)' }], {
        composite: 'add',
        fill: 'both',
        duration: 1500,
    });
});
#box {
  width: 100px;
  height: 100px;
  border: 1px solid black;
 }
<div id="box"></div> 

1

There are 1 best solutions below

2
Nkemdi Anyiam On

Once the first animation finishes, it remains active because of fill: 'forwards'. Then you performed your second animation where you moved the box downwards. Here's where the problem starts: Naturally, you can't actually have 2 separate transforms on the same element's style since it's just a single property, so what's really happening is that once the second animation starts, the first transform is overwritten. The only reason the box is still to the right during the second animation is because of composite: add—it sort of kept that X displacement, if that makes sense.

But then as soon as the second animation actually completes, the animation of course ceases, but because of the fill: 'both' (which could have also been fill: 'forwards'—that makes no difference here), you keep the result of the keyframes you specified for the second animation, which is simply translating Y from 0 to 100 and nothing about X.

There are 2 ways you can fix the example in this specific question. The first involves using commitStyles(), and the second involves using the new individual transform properties available in CSS (and consequently WAAPI).

Solution 1: Use commitStyles()

The simplest solution would be to call commitStyles() on the first animation right after it finishes. This will apply the effects of the X translation to the element's actual inline style, so when the first animation finishes, it essentially "saves" that transform style.

const box = document.getElementById('box');
const animateRight = box.animate(
    [{ transform: 'translateX(0)' }, { transform: 'translateX(200px)' }],
    {
        fill: 'forwards',
        easing: 'ease-in-out',
        duration: 2000,
    }
);
animateRight.finished.then(() => {
  animateRight.commitStyles();
    box.animate([{ transform: 'translateY(0)' }, { transform: 'translateY(100px)' }], {
        composite: 'add',
        fill: 'forwards',
        duration: 1500,
    })
});
#box {
  width: 100px;
  height: 100px;
  border: 1px solid black;
 }
<div id="box"></div>

This approach could be a little janky because of the current issues surrounding fill and the potential of doubling the effect of an animation once commitStyles() is called, but that's beyond the scope of this discussion. If you run into issues like that, you can solve them by setting fill to none immediately after calling commitStyles(). Example:

someAnimation.commitStyles();
someAnimation.effect?.updateTiming({ fill: 'none' });

Solution 2: Use translate instead of transform

In CSS, you can now actually individually set translate, rotate, and scale separately. For example, the following are equivalent:

.thing {
   transform: translate(500px, 200px);
}
.thing2 {
  translate: 500px 200px;
}

Naturally, this means you can set those properties using WAAPI as well. Since they are obviously different properties from transform, you can even use them at the same time. You could have translate, scale, rotate, and transform all acting on the same element at the same time. I removed the composite property in this example to prove this.

const box = document.getElementById('box');
const animateRight = box.animate(
    [{ translate: '0' }, { translate: '200px' }],
    {
        fill: 'forwards',
        easing: 'ease-in-out',
        duration: 2000,
    }
);
animateRight.finished.then(() => {
    box.animate([{ transform: 'translateY(0)' }, { transform: 'translateY(100px)' }], {
        fill: 'forwards',
        duration: 1500,
    });
});
#box {
  width: 100px;
  height: 100px;
  border: 1px solid black;
 }
<div id="box"></div>

Combined with commitStyles() and composite: add (or composite: accumulate), the individual transform properties can be extremely powerful in general.