Script Valley
Web Performance Fundamentals
CSS Performance and RenderingLesson 6.2

How to achieve smooth 60fps CSS animations without jank

16ms frame budget, transform vs positional properties, opacity animation, will-change, GPU layer promotion, containment, requestAnimationFrame for JS animations

Smooth CSS Animations

At 60fps the browser has 16.67ms per frame. Exceed this budget and a frame is dropped โ€” visible as a stutter or jank. The browser's rendering work (style, layout, paint, composite) must fit within that window alongside your JavaScript.

The rule: animate only transform and opacity. These are the only properties that can be promoted to GPU compositor threads and animated without involving the main thread at all.

/* Fade in โ€” composite only */
.fade-in {
  animation: fade 0.3s ease-out;
}
@keyframes fade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Scale on hover โ€” composite only */
.card:hover {
  transform: scale(1.03);
  transition: transform 0.2s ease;
}

If you must animate a layout property (e.g., height for an accordion), use the View Transition API or the FLIP technique (First, Last, Invert, Play) to fake layout changes using transform:

// FLIP: record start position, jump to end, invert with transform, animate to identity
const first = el.getBoundingClientRect();
el.classList.add('expanded');
const last = el.getBoundingClientRect();
const deltaY = first.top - last.top;
el.style.transform = `translateY(${deltaY}px)`;
requestAnimationFrame(() => {
  el.style.transition = 'transform 0.3s ease';
  el.style.transform = '';
});

Enable compositing hints for elements you know will animate:

.modal { will-change: transform, opacity; }

Up next

What causes forced synchronous layout and how to avoid it

Sign in to track progress

How to achieve smooth 60fps CSS animations without jank โ€” CSS Performance and Rendering โ€” Web Performance Fundamentals โ€” Script Valley โ€” Script Valley