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.
#1 Best Overall
- 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 asease-outor 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.
Rank #2
.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.
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
- 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.
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.
Rank #4
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
- Test the complete page, not an isolated code sample, on representative phones, laptops, and browsers.
- Use browser performance tools to record the interaction and look for long scripting, layout, paint, or compositing work.
- Check heavy pages with real content, images, fonts, and other widgets enabled.
- 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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →.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.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
transitionon 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
bothwhen 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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesA 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.
Quick Recap
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.
Recommended Free Tools

