I’m trying to make a CSS animation that bounces back and forth, with a pause at each end. Using the Javascript method element.animate() seems to do the trick, except that using keyframes breaks the easing function, which doesn’t happen when using only CSS.
Am I using animate() wrong? Is it a bug? Why do Javascript and CSS behave differently? The animate MDN docs don’t say anything about this.
const element = document.getElementById("animation")
const anim = element.animate(
[
{ offset: 0, transform: "translateX(0)" },
{ offset: .1, transform: "translateX(0)" },
{ offset: .9, transform: "translateX(50vw)" },
{ offset: 1, transform: "translateX(50vw)" },
],
{
duration: 5000,
easing: "ease-in-out",
direction: "alternate",
iterations: Infinity,
}
);
<div id="animation">Ball</div>
Creating the same animation in CSS works with the correct easing.
const element = document.getElementById("animationJS")
const anim = element.animate(
[
{ offset: 0, transform: "translateX(0)" },
{ offset: .1, transform: "translateX(0)" },
{ offset: .9, transform: "translateX(50vw)" },
{ offset: 1, transform: "translateX(50vw)" },
],
{
duration: 5000,
easing: "ease-in-out",
direction: "alternate",
iterations: Infinity,
}
);
#animationCSS {
animation-name: move;
animation-direction: alternate;
animation-duration: 5s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes move {
from {
transform: translateX(0);
}
10% {
transform: translateX(0);
}
90% {
transform: translateX(50vw);
}
to {
transform: translateX(50vw);
}
}
<div id="animationJS">Ball1</div>
<div id="animationCSS">Ball2</div>
However, I can’t use CSS because I want the keyframe percentages/offsets to be changed programmatically.
Also, removing the middle two keyframes in the JS code fixes it, but it still isn’t what I want.
2
Answers
I've found the solution, the corresponding JS code that works the same as the CSS one.
Adding the easing function only to the middle part of the animation and removing it from the whole does the trick.
I have no idea why it works like this, and I can't find this specific difference in the documentation, but at least it works.
If you remove any of the confusing aspects of the animation (as the easing goes over your whole value range, so it starts at 0 and ends at 1, which means you are missing the first 10% of the ease-in and the last 10% of the ease-out part), and make your values a little bit bigger to really see the easing in action, you will notice that it currently works as expected. It’s easy to get confused by animations, definitely subtle ones.
You can see this in the above snippet: speed up, slow down:
ease-in-out
is working as expected. I can see what you are trying to accomplish though, having a 20% break between animation timelines using a single timeline, but as far as I understand, the easing applies across your whole timeline. So due to that you might want to set up and infinite loop in order to add a delay:Let’s merge your examples
As you can see below, the animations are 99% the same, I suppose the difference is just a slight difference in timing with JS and loading, but they run almost exactly the same (I did have to correct your animation in JS so it had
.9
and not.8
as the offset):Actually, no, the weirdness is still because you trying to add a delay in the loop:
Without that, they are perfectly in sync.