Articles

CSS will-change Explained: Compositing and Performance

The CSS will-change property hints the browser to prepare an element for an upcoming change, moving it to its own compositor layer. When to use it and when not to.

Takina Takina · · 5 min read
Close-up of CSS code in an editor

The CSS will-change property tells the browser ahead of time that an element is about to change — its transform, opacity, scroll position, or another property — so the browser can prepare an optimization strategy, typically promoting the element to its own compositor layer before the change happens rather than reacting to it after the fact.

What it actually does

Browsers render pages in layers. Most elements share a layer with their surroundings, but certain properties — transform and opacity in particular — can be animated purely on the compositor thread, without re-running layout or paint, if the element being animated has its own layer. Creating that layer costs time and memory, though, so browsers don’t give every element one by default.

will-change is a hint that says: promote this element now, before the animation starts, instead of discovering mid-animation that a layer is needed and creating it under time pressure — which can cause a visible stutter on the first frame.

.card {
  will-change: transform;
}

.card:hover {
  transform: scale(1.05);
}

Without will-change, the first hover might still trigger layer promotion, just later and potentially with a frame or two of jank while the browser sets it up. With it, the layer is ready before the transform ever runs.

The cost of overusing it

will-change isn’t free, and it isn’t a general-purpose “make this fast” switch. Every element it’s applied to gets its own compositor layer, and each layer consumes GPU memory. Apply it broadly — to every card in a long list, or leave it on an element permanently instead of only during the interaction — and a page can end up with dozens of layers competing for the same limited GPU memory, which can make performance worse than doing nothing at all.

The property is meant to be applied shortly before a change happens and removed shortly after, not left on indefinitely:

.card {
  transition: transform 0.2s;
}

.card.is-dragging {
  will-change: transform;
}

Here the class only gets added while the drag interaction is actually active, so the layer exists only when it’s needed.

What belongs in the value

will-change accepts specific property names (transform, opacity, scroll-position, contents) or the keywords auto and scroll-position. Listing a property tells the browser exactly what’s about to change, so it can pick the cheapest strategy for that specific case. Avoid the temptation to just throw will-change: transform, opacity, top, left at an element for everything it might ever do — the more properties listed, the more aggressive the optimization the browser has to prepare for, which works against the goal.

Also avoid animating top, left, width, or height in the first place if performance matters — those trigger layout recalculation on every frame regardless of will-change, which is a separate problem will-change doesn’t solve. The fix there is to animate transform instead, which will-change genuinely helps with. See the critical rendering path for why layout-triggering properties are expensive in the first place.

will-change vs just using transform/opacity

transform / opacity aloneWith will-change
Layer promotionHappens on first animation frame if neededHappens ahead of time
First-frame jank riskPossible, on complex or first-run animationsReduced
Memory costNone until animatedOngoing, for as long as the property is set
Best usage patternFine for simple, infrequent animationsAdd just before a known upcoming change, remove after

When to actually reach for it

will-change earns its cost in a narrow set of cases: an element about to be dragged, a modal about to slide in, an element whose animation is known to be complex enough that first-frame jank is visible and measured, not just suspected. It’s a tool for a specific, observed problem — not a default to sprinkle across a stylesheet. Pair any use of it with browser devtools’ performance panel or Core Web Vitals measurement to confirm it’s actually helping before keeping it in the codebase, and prefer scoping it with a class that’s added and removed around the interaction, as shown above, over leaving it permanently attached to a selector. It composes naturally with CSS transitions and animations and with scroll-driven animations, which both rely on the same compositor-layer mechanics underneath.

Common mistakes to avoid

A few patterns consistently undermine will-change rather than helping it:

  • Setting it in a static stylesheet rule with no removal. If will-change is applied via a plain selector that always matches, the browser treats the element as permanently about to change, keeping its layer allocated for the page’s entire lifetime even when nothing is animating.
  • Applying it to too many elements at once. A hint on one card in a grid is cheap; the same hint applied to every card in that grid multiplies the memory cost by the number of cards, often for no measurable benefit.
  • Using it as a substitute for fixing a slow animation. If an animation is janky because it’s animating a layout-triggering property like width or top, will-change won’t fix that — the underlying property choice needs to change first.
  • Adding it reflexively without profiling. The only reliable way to know whether will-change is helping is to measure frame timing before and after; assuming it helps because the property name sounds relevant is how pages accumulate unnecessary layers.

The takeaway

will-change hints the browser to promote an element to its own compositor layer before a change happens, avoiding first-frame stutter on transform and opacity animations. It costs GPU memory per layer, so it should be applied narrowly and temporarily — just before a known interaction, removed just after — rather than left on by default. Used sparingly and measured against real performance data, it’s a precise fix for a specific class of animation jank; used broadly, it can quietly make a page slower.

Takina Takina · · 4 min read

CSS Scroll-Driven Animations Explained

CSS scroll-driven animations tie keyframes to scroll position instead of a clock, running smoothly off the main thread. Here's how the timeline model works.

#CSS #Web Development #Frontend
Takina Takina · · 4 min read

What Is the Beacon API? navigator.sendBeacon()

The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.

#Web Development #Frontend #Performance