Web animation performance guidance for avoiding layout thrashing and reaching smooth 60/120fps motion with compositor-friendly techniques.
Real-world examples not captured yet
This skill shows a gallery of live HTML demos (CDN libraries welcome — GSAP, Three.js, etc.) rendered inline via srcdoc.
Skill markdown
# Performant Web Animation
Eliminate the #1 cause of janky web animation: animating properties that force the browser to recalculate layout (reflow) or repaint on every frame. The browser renders in stages — **layout → paint → composite**. Animating `width`, `height`, `top`, `left`, `margin`, `padding`, `box-shadow`, or `filter` re-runs layout and/or paint each frame, blocking the main thread. Animating **`transform`** and **`opacity`** runs entirely on the compositor (often the GPU), skipping layout and paint, which is what makes animation smooth at 60/120fps.
## When to use
Use when an animation stutters or drops frames, when a hover/scroll effect feels heavy, when animating size/position/shadow, when a layout change needs to animate smoothly (cards reordering, an element moving between containers), when animating `height: auto`, or when reviewing animation code for performance.
## Core rule: animate only transform and opacity
Map every "expensive" animation to a cheap equivalent.
| Animating (expensive) | Triggers | Replace with (cheap) |
|---|---|---|
| `width` / `height` | Layout + Paint | `transform: scaleX()/scaleY()` (+ FLIP for true size) |
| `top` / `left` / `margin` | Layout + Paint | `transform: translate()` |
| `box-shadow` | Paint | Animate `opacity` of a pseudo-element holding the shadow |
| `filter: blur()` | Paint (heavy) | Cross-fade two layers via `opacity`, or accept sparingly |
| `background-position` | Paint | `transform: translate()` on a child layer |
| `color` / `background` | Paint | Often acceptable; or cross-fade layers |
### Position and size via transform
```css
/* BAD: animates layout every frame */
.box { transition: left 300ms, width 300ms; left: 0; width: 100px; }
.box:hover { left: 200px; width: 200px; }
/* GOOD: compositor-only */
.box {
transition: transform 300ms ease;
transform: translateX(0) scaleX(1);
transform-origin: left center;
}
.box:hover { transform: translateX(200px) scaleX(2); }
```
`scaleX` distorts inner content (text stretches). For true resizing without distortion, use **FLIP**.
## Cheap box-shadow via pseudo-element opacity
Animating `box-shadow` repaints a large blurred region every frame. Instead paint the shadow once on a `::after`, then animate only its `opacity`.
```css
.card { position: relative; }
.card::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: 0 12px 28px rgba(0,0,0,0.35);
opacity: 0;
transition: opacity 300ms ease;
pointer-events: none;
}
.card:hover::after { opacity: 1; }
```
The blurred shadow is rasterized once; hovering only changes a compositor opacity — smooth at any frame rate.
## FLIP: animate layout changes cheaply
FLIP (First, Last, Invert, Play) animates a *layout* change (reorder, resize, move between containers) using only `transform`. Measure where the element was (First) and will be (Last), apply an inverting transform so it visually appears unmoved, then animate the transform back to identity (Play). The DOM ends in its real final layout; the motion is pure compositor work.
```js
function flip(el, mutate) {
const first = el.getBoundingClientRect(); // First
mutate(); // change the DOM/layout
const last = el.getBoundingClientRect(); // Last
const dx = first.left - last.left;
const dy = first.top - last.top;
const sx = first.width / last.width;
const sy = first.height / last.height;
el.animate(
[
{ transformOrigin: 'top left',
transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})` }, // Invert
{ transformOrigin: 'top left', transform: 'none' }, // Play
],
{ duration: 300, easing: 'cubic-bezier(0.2, 0, 0, 1)' }
);
}
// usage: animate an element moving to a new grid position
flip(card, () => targetContainer.appendChild(card));
```
The Web Animations API `animate()` runs the transform on the compositor. For many elements, batch all `getBoundingClientRect()` reads *before* any mutation (see layout thrashing below).
## Animating height: auto
`height: auto` is not interpolatable historically. Modern and fallback approaches:
```css
/* Modern (Chrome 129+/supporting browsers): opt size keywords into animation */
.panel {
interpolate-size: allow-keywords; /* enables animating to/from auto */
height: 0;
overflow: clip;
transition: height 300ms ease;
}
.panel.open { height: auto; }
/* calc-size() also works: height: calc-size(auto, size); */
```
```css
/* Robust fallback today: CSS grid 1fr -> 0fr */
.wrapper {
display: grid;
grid-template-rows: 0fr; /* collapsed */
transition: grid-template-rows 300ms ease;
}
.wrapper.open { grid-template-rows: 1fr; }
.wrapper > .content { overflow: hidden; min-height: 0; }
```
The grid trick animates the track size (compositor-friendly enough and broadly supported) and needs no JS height measurement. Use `interpolate-size: allow-keywords` / `calc-size(auto, size)` where the tar
…More from iart-ai
- Accessible AnimationTiered reduced-motion patterns for CSS, GSAP, Framer Motion, Lenis, and other web animation systems.
- Ascii AnimationThis skill should be used when the user asks to "make an ASCII animation", "build a terminal/CLI intro or loader", "convert an image or video to ASCII art", "add an ASCII shader/post-effect to a canvas or Three.js scene", "create retro/hacker text-character motion", or "animate text characters with a brightness ramp". Covers generative ASCII fields, image/video/3D-to-ASCII, and animated character art for both web and terminal.
- GlassmorphismThis skill should be used when the user asks to "add a glassmorphism effect", "frosted glass UI", "Apple liquid glass style", "frosted blur card", "translucent glass panel animation", "make a frosted nav bar", "build a glass modal/dialog", "animate a glass card on hover", or "add a refracting liquid-glass hero". Covers frosted translucent panels with backdrop-filter blur, edge/specular highlights, SVG liquid-glass refraction, motion on hover/scroll/enter, and accessible reduced-transparency fallbacks.
- GSAP WebGSAP guidance for code-driven web motion, including timelines, ScrollTrigger, SplitText, Flip, and smooth-scroll synchronization.
- Lottie AnimationLottie and dotLottie integration guidance for playback control, interactivity, runtime theming, and cross-platform export workflows.
- Micro InteractionUI motion guidance for hover and press feedback, toggles, toasts, drawers, modals, list transitions, and shared-element interactions.