Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use CSS transitions for a change between two states, CSS keyframes for a defined sequence, the Web Animations API (WAAPI) when JavaScript must control playback, and requestAnimationFrame() for custom per-frame work such as canvas drawing. Start with CSS for ordinary interface motion, then profile the real effect on representative devices instead of assuming that CSS or JavaScript is always faster.

Choose the animation tool before writing code

The right API depends on the shape of the interaction and who needs to control it.

Technique Best fit Typical control Avoid when
CSS transition A property moving from its current value to one target value, such as hover, focus, expansion, or an enabled state Change a class, pseudo-class, or inline style You need multiple waypoints, imperative playback, or coordinated timelines
CSS keyframe animation A repeatable sequence with intermediate states, delays, direction, easing, or iteration counts @keyframes plus animation-* properties JavaScript must frequently retime, pause, reverse, or synchronize it with application state
Web Animations API DOM animation whose timing and playback are decided dynamically Element.animate() returns an Animation object with methods such as pause(), play(), and reverse() A simple state change can be expressed clearly with CSS alone
requestAnimationFrame() Custom per-frame updates, simulations, or canvas rendering Run a callback immediately before repaint and update your drawing or state You are merely animating ordinary DOM styles that CSS or WAAPI can handle

These are browser-native choices, not competing branding labels. Rendering cost depends on the properties changed, the amount of work, and the device.

Build a two-state effect with a CSS transition

A transition interpolates between the current computed style and a new style. If the target changes while the transition is running, the browser can continue from the current interpolated value rather than jumping back to the original state.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

HTML

<button class="card" type="button">
  View details
</button>

CSS

.card {
  display: inline-block;
  padding: 1rem 1.25rem;
  border: 1px solid #cbd5e1;
  border-radius: .75rem;
  background: white;
  color: #0f172a;
  transform: none;
}

@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 180ms ease-out, box-shadow 180ms ease-out;
  }

  .card:hover,
  .card:focus-visible {
    transform: translateY(-0.25rem);
    box-shadow: 0 .5rem 1.25rem rgb(15 23 42 / .18);
  }
}

.card:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 3px;
}

The motion is limited to pointer hover and keyboard focus, while the outline remains available in every motion mode. Keep the non-animated state understandable: a keyboard user should not need to see movement to know what the control does.

Useful transition properties

  • transition-property: the properties to interpolate.
  • transition-duration: how long each change takes.
  • transition-timing-function: acceleration, such as ease-out or a custom cubic Bézier curve.
  • transition-delay: time before interpolation begins.

Do not transition every property by default. Name the properties that should move, and verify that the resulting interaction still works when a target changes quickly.

Use keyframes for a multi-step sequence

Keyframes describe visual waypoints rather than just a start and an end. The animation can run once, repeat, reverse direction, or retain its final state.

.notice {
  animation-name: enter-and-settle;
  animation-duration: 520ms;
  animation-timing-function: cubic-bezier(.2, .8, .2, 1);
  animation-delay: 80ms;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: both;
}

@keyframes enter-and-settle {
  0% {
    opacity: 0;
    transform: translateY(1rem) scale(.98);
  }
  70% {
    opacity: 1;
    transform: translateY(-.15rem) scale(1.01);
  }
  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .notice {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

For a looping background effect, set animation-iteration-count: infinite, but do not make important information depend on a loop. Persistent or user-triggered movement should have a pause or disable control where appropriate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Control a DOM animation with the Web Animations API

Element.animate() starts an animation and returns an Animation object. That object lets application code pause, resume, reverse, cancel, and await completion.

<button id="play" type="button">Play</button>
<button id="pause" type="button">Pause</button>
<button id="reverse" type="button">Reverse</button>
<div id="panel" aria-hidden="true">Panel</div>

<script>
const panel = document.querySelector("#panel");
const play = document.querySelector("#play");
const pause = document.querySelector("#pause");
const reverse = document.querySelector("#reverse");
const motionAllowed = window.matchMedia(
  "(prefers-reduced-motion: no-preference)",
).matches;

const animation = panel.animate(
  [
    { opacity: 0, transform: "translateY(1rem)" },
    { opacity: 1, transform: "translateY(0)" },
  ],
  { duration: 220, easing: "ease-out", fill: "both" },
);

if (!motionAllowed) {
  animation.finish();
}

play.addEventListener("click", () => animation.play());
pause.addEventListener("click", () => animation.pause());
reverse.addEventListener("click", () => animation.reverse());
animation.finished.then(() => panel.setAttribute("aria-hidden", "false"));
</script>

In a production component, also listen for changes to the media query so a preference change while the page is open updates the animation policy. Ensure that canceling or disabling motion leaves the content visible and usable.

Rank #3
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Use requestAnimationFrame for custom per-frame work

Use requestAnimationFrame() when each frame requires JavaScript calculation, such as drawing a canvas scene or integrating a custom simulation. The callback is scheduled before repaint and is generally more appropriate for animation than timer loops built with setTimeout() or setInterval().

const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let start;
let frameId;

function draw(timestamp) {
  if (start === undefined) start = timestamp;
  const elapsed = timestamp - start;
  const progress = Math.min(elapsed / 1000, 1);
  const x = progress * (canvas.width - 40);

  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle = "royalblue";
  context.fillRect(x, 20, 40, 40);

  if (progress < 1) frameId = requestAnimationFrame(draw);
}

frameId = requestAnimationFrame(draw);
// To stop early: cancelAnimationFrame(frameId);

Use the timestamp supplied to the callback so motion is based on elapsed time rather than assuming every device delivers the same number of frames. Cancel scheduled work when the component is removed or the effect is no longer needed.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make motion smooth without promising a benchmark

MDN uses 60 frames per second as a commonly accepted smooth-animation reference. At that nominal rate, one frame is about 16.7 milliseconds, including script, style calculation, layout when needed, and repaint. These are reference figures, not a guaranteed result or a universal threshold.

Prefer properties that avoid unnecessary layout

Animating a property can trigger style recalculation, layout, paint, composition, or several of those stages. A large number of active animations or an expensive property can make the page less responsive. Transforms and opacity are often practical choices, but the property name alone does not prove an effect is cheap on every page.

Measure the actual interaction

  1. Test the complete page, not an isolated code sample, on representative phones, laptops, and browsers.
  2. Use browser performance tools to record the interaction and look for long scripting, layout, paint, or compositing work.
  3. Check heavy pages with real content, images, fonts, and other widgets enabled.
  4. Reduce simultaneous effects, simplify selectors, or move custom drawing off the main path when profiling shows a bottleneck.

CSS is a strong starting point for simple effects, but CSS and JavaScript can be close when both run on the main thread. The rendering path and the work performed determine the result.

Respect prefers-reduced-motion

Honor the operating system or browser preference with CSS and JavaScript. A common pattern is to make nonessential motion opt-in only when the preference is no-preference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
.hero {
  transform: none;
}

@media (prefers-reduced-motion: no-preference) {
  .hero {
    transition: transform 180ms ease-out;
  }

  .hero:hover,
  .hero:focus-visible {
    transform: translateY(-0.25rem);
  }
}
const motionAllowed = window.matchMedia(
  "(prefers-reduced-motion: no-preference)",
).matches;

if (motionAllowed) {
  element.animate(
    [{ opacity: 0 }, { opacity: 1 }],
    { duration: 180, easing: "ease-out" },
  );
}

Reduced motion is a user-preference technique, not proof that a site as a whole conforms to accessibility requirements. W3C WAI describes its techniques as examples rather than required implementations of WCAG. Remove or substantially reduce nonessential movement, keep content available, and provide pause or disable controls for persistent motion.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Debug common animation failures

The transition does not run

  • Cause: the property is not interpolable, the element has no changed computed value, or the transition is placed only on the hover rule.
  • Fix: put transition on the base rule, verify the property values in computed styles, and test a numeric, color, opacity, or transform change.

The keyframe starts from the wrong state

  • Cause: a competing declaration or missing animation-fill-mode.
  • Fix: inspect the cascade and use both when the first and last keyframe states should apply outside the active interval.

WAAPI controls appear to do nothing

  • Cause: the animation was already finished, canceled, or immediately forced to its end state for reduced motion.
  • Fix: inspect animation.playState, keep the returned object, and decide explicitly how play, pause, reverse, and cancel should behave.

The page stutters

  • Cause: expensive layout or paint work, too many simultaneous effects, or a per-frame callback doing more work than the frame budget allows.
  • Fix: profile, animate fewer elements, prefer simpler properties where appropriate, and throttle or cancel work that is no longer visible.

Motion is inaccessible

  • Cause: important information is conveyed only through movement, keyboard focus is missing, or reduced-motion users receive the same persistent effect.
  • Fix: preserve a static meaning, style :focus-visible, honor the media preference, and add a pause or disable control for persistent motion.

Or skip the browser setup

If your goal is to automate screenshots of an animated page rather than build the animation itself, ScreenshotNeo provides a single website screenshot API request. It can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result.

After creating an API key, see the ScreenshotNeo documentation. This cURL request captures a WebP image:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It supports full-page and element captures, device presets, custom viewports, dark mode, retina scale, PDF options, custom CSS and JavaScript, click and wait actions, resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is on every plan. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A practical decision checklist

  • Is this one state changing to another? Start with a transition.
  • Does it require waypoints, looping, or direction? Use keyframes.
  • Must code pause, reverse, retime, or coordinate playback? Use WAAPI.
  • Are you drawing or simulating every frame? Use requestAnimationFrame().
  • Have you defined a static, keyboard-friendly state and a reduced-motion path?
  • Have you profiled the finished interaction on the devices your audience uses?

Frequently Asked Questions

Can CSS and JavaScript animate the same element?

Yes, but define ownership clearly. Competing transforms, animation declarations, or WAAPI effects can override one another; use one source of truth for each property and coordinate handoffs deliberately.

Should I use setInterval for a JavaScript animation?

For custom visual updates, prefer requestAnimationFrame(), which is scheduled in relation to repaint and can be canceled when the work is no longer needed.

Does reduced motion mean every animation must disappear?

Nonessential motion should be removed or substantially reduced, while essential state changes and understandable content must remain available. Persistent motion may also need a pause or disable control.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.