Animate on the compositor
Animate transform and opacity on rAF; stretch very fast objects.
From apple-design, §11, after Designing Fluid Interfaces ↗.
left vs transform under load
Block the main thread and watch which box stutters.
Longest frame –
Animating
leftBadleft: 0 → 100%Animating
transformGoodtransform: translateX().box { will-change: transform; }
box.animate(
[{ transform: "translateX(0)" }, { transform: `translateX(${travel}px)` }],
{ duration: 1600, iterations: Infinity, direction: "alternate",
easing: "cubic-bezier(0.77, 0, 0.175, 1)" }
);
setInterval vs rAF
Each tick is one display frame. Compare the spacing.
Display –
Timer-drivenBad
setInterval(step, 16)–Display-syncedGood
requestAnimationFrame(step)–let last = performance.now();
function step(now) {
const dt = (now - last) / 1000;
last = now;
x = advance(x, dt);
el.style.transform = `translateX(${x}px)`;
requestAnimationFrame(step);
}
requestAnimationFrame(step);
Stretch fast motion
Raise the speed until the left dot splits into ghosts.
Jump per frame –
Sharp dotBad
rotate(θ) translateX(r)Stretched by velocityGood
… scale(1/√s, s)const step = speed * dt;
const s = 1 + Math.min(2, Math.max(0, step / size - 0.3) * amount);
dot.style.transform =
`rotate(${angle}rad) translateX(${r}px) scale(${1 / Math.sqrt(s)}, ${s})`;
Why it matters, pros and cons
Why it matters
transform and opacity skip layout, and rAF gives each frame one new position.
Pros
- Survives main-thread jank.
- rAF adapts to 60, 90 and 120 Hz.
- Stretched flings read as one object.
Cons
- rAF-driven transforms still stall on a blocked thread.
will-changecosts GPU memory per layer.- Some effects genuinely need layout, like reflowing text.