ChristianW_Dev
ChristianW_Dev

Reputation: 15

Why is my JS Animation behaving differently than my CSS Animation?

The JS Animation is using the built in JS Animate function https://developer.mozilla.org/en-US/docs/Web/API/Element/animate

myRef.current.animate(
                    [
                        {transform: `translatey(32.5px)`, offset: 0}, 
                        {transform: `translatey(-2260px)`, offset: 0.9},
                        {transform: `translatey(-2260px)`, offset: 0.95}, 
                        {transform: `translatey(-2247.5px)`, offset: 1}
                    ], 
                    {
                        duration: 4000,
                        easing: 'cubic-bezier(0.33, 1, 0.68, 1)',
                        fill: 'forwards',
                        iterations: 1,
                    }
                )

vs

                    .test {
                        animation: 4s cubic-bezier(0.33, 1, 0.68, 1) forwards spin1;
                    }

                    @keyframes spin1 {
                        0% {
                            transform: translatey(32.5px);
                        }
                        90% {
                            transform: translatey(-2260px);
                        }
                        95% {
                            transform: translatey(-2260px);
                        }
                        100% {
                            transform: translatey(-2247.5px);
                        }
                    }

Expected same result from both but for some reason the JS one snaps onto the offsets, while the CSS one is smoother and doesn't snap.

Upvotes: 0

Views: 55

Answers (1)

Kaiido
Kaiido

Reputation: 137133

This is because in CSS, the animation-timing-function affects each KeyFrame instead of the whole animation:

.anim {
  height: 300px;
  background: pink;
  border: 2px solid purple;
  animation: 8s cubic-bezier(0, -1, 1, 0) forwards anim;
}

@keyframes anim {
  0%   { width:  50px }
  25%  { width: 150px }
  50%  { width: 250px }
  75%  { width: 350px }
  100% { width: 450px }
}
Here you can see the <code>animation-timing-function</code> has been applied 4 times.
<div class="anim"></div>

The easing property you've set in your KeyframeEffect option object is the one that applies to the entire iteration duration of the keyframe effect. This doesn't apply on each KeyFrame, but on the whole animation:

const el = document.querySelector(".anim");
el.animate(
  [
    { width:  "50px" },
    { width: "150px" },
    { width: "250px" },
    { width: "350px" },
    { width: "450px" },
  ],
  {
    duration: 8000,
    fill: "forwards",
    easing: "cubic-bezier(0, -1, 1, 0)",
  },
);
.anim {
  height: 300px;
  background: pink;
  border: 2px solid purple;
}
Here you can see the <code>easing</code> timing function has been applied only once.
<div class="anim"></div>

To get the same effect as with CSS animation-timing-function, you need to set it to all your KeyFrame objects.

const el = document.querySelector(".anim");
el.animate(
  [
    { width:  "50px", easing: "cubic-bezier(0, -1, 1, 0)" },
    { width: "150px", easing: "cubic-bezier(0, -1, 1, 0)" },
    { width: "250px", easing: "cubic-bezier(0, -1, 1, 0)" },
    { width: "350px", easing: "cubic-bezier(0, -1, 1, 0)" },
    { width: "450px", easing: "cubic-bezier(0, -1, 1, 0)" },
  ],
  {
    duration: 8000,
    fill: "forwards"
  },
);
.anim {
  height: 300px;
  background: pink;
  border: 2px solid purple;
}
Here you can see the <code>easing</code> timing function has been applied 4 times.
<div class="anim"></div>

Or to avoid repeating many times the same values:

const el = document.querySelector(".anim");
el.animate(
  {
    width: [ "50px", "150px", "250px", "350px" ],
    easing: [ "cubic-bezier(0, -1, 1, 0)" ],
  },
  {
    duration: 8000,
    fill: "forwards"
  },
);
.anim {
  height: 300px;
  background: pink;
  border: 2px solid purple;
}
Here you can see the <code>easing</code> timing function has been applied 4 times.
<div class="anim"></div>

Upvotes: 1

Related Questions