← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Web Animations

Web Animations

Animating elements with JavaScript — the Web Animations API, requestAnimationFrame, CSS transitions from JS, and GSAP.

What it is, in plain English: The Web Animations API (WAAPI) gives JavaScript direct control over CSS animations. You can create, pause, reverse, seek, and speed up animations programmatically. element.animate() is the key method — it takes keyframes and timing options and returns an Animation object. For complex animations, requestAnimationFrame gives you frame-by-frame control.
Where this bites you

transitionend fires once for each property that transitions. Animate transform and opacity together and the listener runs twice. Cleanup code (removing the element, starting the next step, counting completions) runs twice too. Calling remove() twice happens to be harmless, but most follow-up actions aren't.

// <div class="toast">Saved <button class="close">×</button></div>
const toast = document.querySelector(".toast");
let cleanups = 0;

toast.querySelector(".close").addEventListener("click", () => {
  toast.style.transition = "transform 0.3s ease, opacity 0.3s ease";
  toast.style.transform  = "translateX(200px)";
  toast.style.opacity    = "0";
});

toast.addEventListener("transitionend", (event) => {
  cleanups++;
  console.log("transitionend:", event.propertyName, "| cleanup runs:", cleanups);
  toast.remove();
});
// transitionend: opacity | cleanup runs: 1
// transitionend: transform | cleanup runs: 2

Animating elements with JavaScript

element.animate() — the Web Animations API

const element = document.querySelector(".box");

// animate(keyframes, options):
const animation = element.animate(
  [
    { transform: "scale(1)",   opacity: 1 },   // start
    { transform: "scale(1.5)", opacity: 0.5 }, // middle (optional)
    { transform: "scale(1)",   opacity: 1 },   // end
  ],
  {
    duration:   600,     // milliseconds
    easing:     "ease",  // "linear", "ease", "ease-in", "ease-out", "ease-in-out"
    delay:      200,     // delay before starting
    iterations: 1,       // 1 = once, Infinity = loop
    fill:       "forwards",  // "none", "forwards" (keep end state), "backwards", "both"
  }
);

// Control playback:
animation.pause();
animation.play();
animation.reverse();
animation.cancel();
animation.finish();  // jump to end

// Events:
animation.addEventListener("finish",  () => console.log("Done!"));
animation.addEventListener("cancel",  () => console.log("Cancelled"));

// Return value check:
const anims = element.getAnimations();  // all running animations on this element

CSS transitions from JavaScript

// Apply a CSS transition, then change the property:
element.style.transition = "transform 0.3s ease, opacity 0.3s ease";
element.style.transform  = "translateX(200px)";
element.style.opacity    = "0";

// Listen for transition end:
element.addEventListener("transitionend", (event) => {
  console.log("Transition finished:", event.propertyName);
  element.remove();
});

The example above logs event.propertyName for exactly this reason. When several properties transition together, pick one to react to and ignore the others. ({ once: true } isn't a reliable alternative: it reacts to whichever property finishes first, so it can fire before the others are done when the durations differ.)

const toast = document.querySelector(".toast");
let cleanups = 0;

toast.querySelector(".close").addEventListener("click", () => {
  toast.style.transition = "transform 0.3s ease, opacity 0.3s ease";
  toast.style.transform  = "translateX(200px)";
  toast.style.opacity    = "0";
});

toast.addEventListener("transitionend", (event) => {
  if (event.propertyName !== "opacity") return;   // react to ONE property only
  cleanups++;
  console.log("transitionend:", event.propertyName, "| cleanup runs:", cleanups);
  toast.remove();
});
// transitionend: opacity | cleanup runs: 1

requestAnimationFrame — frame-by-frame control

// Use for game loops, physics, anything that updates every frame:
let x = 0;

function update(timestamp) {
  x = (x + 1) % window.innerWidth;
  element.style.transform = `translateX(${x}px)`;
  requestAnimationFrame(update);  // schedule next frame
}

const frameId = requestAnimationFrame(update);

// Cancel the animation:
// cancelAnimationFrame(frameId);
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.