Search code examples
javascriptcsscss-animations

element.animate() using Keyframes seems to apply easing function incorrectly


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.


Solution

  • I've found the solution, the corresponding JS code that works the same as the CSS one.

    const element = document.getElementById("animationJS")
    const anim = element.animate(
      [
        { offset: 0, transform: "translateX(0)" },
        { offset: .1, transform: "translateX(0)", easing: "ease-in-out" },
        { offset: .9, transform: "translateX(50vw)"},
        { offset: 1, transform: "translateX(50vw)" },
      ],
      {
        duration: 5000,
        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>

    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.