Animations vs transitions
Both move things, but they’re for different situations:
- Transitions animate between two states and need a trigger (
:hover, a class toggle). Perfect for simple state changes. - Animations use
@keyframesto define multi-step sequences that can run on load, loop forever, and describe complex motion. Perfect for entrances, spinners, and continuous effects.
This generator builds the @keyframes and the animation shorthand (name duration timing-function delay iteration-count direction fill-mode) with a live preview.
Animate only transform and opacity
The single most important performance rule: animate transform and opacity, nothing else. These run on the GPU compositor thread, so they hit 60fps even when the main thread is busy and never trigger layout. Animating width, height, margin, top, or left forces the browser to recalculate layout every frame (reflow) and stutters. The substitutions:
| Instead of animating | Use |
|---|---|
left / top | transform: translate() |
width / height | transform: scale() |
| layout properties | a transform equivalent |
Add will-change: transform as a hint when you know an element is about to animate.
Fill-mode and smooth loops
animation-fill-mode decides what styles apply outside the run: forwards holds the final keyframe (most entrance animations want this, or the element snaps back), backwards applies the first keyframe during the delay. For a seamless infinite loop, make the 0% and 100% keyframes identical (e.g. rotate(0deg) to rotate(360deg)), or use direction: alternate so it reverses each cycle instead of jumping.
The replay gotcha
Re-applying the same animation name doesn’t restart it. To replay on demand, remove the animation, force a reflow, then re-add it: el.style.animation = 'none'; void el.offsetHeight; el.style.animation = ''. Build and preview your curve here — then add the prefers-reduced-motion guard before shipping so the motion is inclusive by default.