will-change Values Beyond transform

Part of Layer Promotion & will-change Strategy in Performance Budgeting & GPU Architecture.

The problem

A stylesheet contains will-change: transform on cards that only ever animate opacity, will-change: all on a modal, will-change: contents on a live-updating counter someone found in a blog post, and will-change: scroll-position on a sidebar that does not scroll. Memory usage is high, the Layers panel shows layers for elements that never move, and nobody can explain what most of these hints are meant to do.

will-change is not a generic “make this fast” switch. Each value tells the browser to prepare for a specific kind of change, and most values do not create a layer at all.

Root cause analysis: a hint about what will change, not how fast

will-change lets an author say which properties are about to change so the browser can do preparatory work ahead of time. What that work is depends on the value.

transform, opacity, filter, backdrop-filter. These are the compositing-relevant values. Naming them typically promotes the element to its own layer so the property can be animated without repainting. This is the case the property is famous for, and the one that costs texture memory.

contents. Tells the browser that the element’s contents will change frequently, so it should avoid caching rendering of the subtree. It does not promote a layer; it disables an optimisation. On an element whose contents change every frame it can help; on anything else it makes rendering slower.

scroll-position. Tells the browser that the element’s scroll position will change, so it can prepare content just outside the visible area of a scroll container. It is only meaningful on actual scroll containers, and only before programmatic scrolling or momentum.

Custom properties. will-change: --my-angle is valid and hints that a registered custom property will change. The useful effect is limited; the property’s own cost, described in animation composition with registered custom properties, is style recalculation per frame, which a hint does not remove.

all. Cannot be acted on usefully — the browser cannot prepare for everything — and browsers may ignore it or promote unnecessarily. It is always the wrong choice.

Naming the wrong property costs without benefiting. will-change: transform on an element that animates opacity still promotes a layer, so the memory is spent, but the hint tells the browser to prepare for a transform that never comes.

What each will-change value preparesOnly the compositing values create layers; the others change different behaviour.What each will-change value preparesCreates a layer?What it preparesWhen it helpstransform /opacityUsuallyCompositor animationJust before moving or fadingfilter /backdrop-filterUsuallyFilter pipelineBefore animating a filtercontentsNoSkips caching the subtreeContents change every framescroll-positionNoOff-screen scroll contentBefore programmatic scrollingcustom propertyNoLittle in practiceRarelyallMaybeNothing usefulNever
Only the compositing values create layers; the others change different behaviour.

Step-by-step resolution

Auditing will-change usageMatch every hint to a real, imminent change.Auditing will-change usage1List every will-change declaration with the property it names.An inventory2For each, find the animation that changes that property.Hints without animations are dead3Delete will-change: all and hints on non-animating elements.Immediate memory saving4Replace mismatched values with the properties actually animated.Correct preparation5Scope remaining hints to a state or an approach condition.Promotion only when needed6Confirm the layer count drops in the Layers panel.
Match every hint to a real, imminent change.

Production code pattern

/* Correct: the hint names what actually animates, and only while it does. */
.card {
  transition: opacity var(--motion-enter-duration) linear;
}
.card[data-entering] {
  will-change: opacity;                        /* not transform: nothing moves */
}

/* A drawer that both moves and fades: name both. */
.drawer[data-animating] {
  will-change: transform, opacity;
}

/* A scroll container about to be scrolled programmatically. */
.chat-log[data-jumping] {
  will-change: scroll-position;                /* prepare off-screen content, no layer */
}

/* A counter whose text changes every frame during a countdown. */
.countdown[data-running] {
  will-change: contents;                       /* skip caching; measure before keeping this */
}

/* Never: */
/* .modal { will-change: all; } */

@media (prefers-reduced-motion: reduce) {
  .card[data-entering], .drawer[data-animating] { will-change: auto; }
}
// Hints are added on approach and removed when the work is done.
drawer.addEventListener('pointerenter', () => drawer.toggleAttribute('data-animating', true));
drawer.addEventListener('transitionend', () => drawer.removeAttribute('data-animating'));

Rendering Impact: promotion while the hint applies, for the compositing values only. contents and scroll-position change other internal behaviour and allocate no texture, so they are cheap to try and easy to leave in place by mistake.

Removing the hints under reduced motion is worth doing: if the animation does not run, the preparation is pure cost.

Texture memory on a list page, by will-change usage60 cards, 412 by 915 viewport at DPR 2.6.Texture memory on a list page, by will-change usagewill-change: transform on every card58 MBwill-change: opacity on every card58 MBHint only while entering9 MBNo hints4 MB
60 cards, 412 by 915 viewport at DPR 2.6.

When no hint is better

Browsers already promote elements that are running compositor animations. For a transition triggered by hover or a class change, the promotion happens as the animation starts, and the only thing will-change buys is preparing the layer slightly earlier — useful when the very first frame must be perfect, and irrelevant otherwise.

The cases where the hint genuinely helps are narrow: an element that will animate in response to an imminent user action, hinted on approach (hover, focus, or entering the viewport); and an element whose first frame currently shows a visible hitch in a trace. Everything else is memory spent on a guess, which is why the promotion lifecycle matters more than the hint itself.

Verification checklist

Constraints and trade-offs

  • Promotion always costs texture memory proportional to the element’s area.
  • contents disables caching, which can make a mostly static subtree slower.
  • scroll-position only applies to scroll containers and has no effect elsewhere.
  • Browsers may ignore hints under memory pressure, so they are not a guarantee.
  • Adding a hint and the animation in the same frame gives the browser no time to prepare.

Frequently asked questions

Does will-change: opacity create a layer?

Usually yes, in the same way as transform: the element is promoted so opacity can be animated without repainting.

What does will-change: contents do?

It tells the browser the element’s contents will change often, so it should not cache the subtree’s rendering. It does not create a layer and can make static content slower.

Is will-change: all ever useful?

No. The browser cannot prepare for every possible change, and the hint wastes resources or causes unnecessary promotion.

Should I add will-change to everything that animates?

No. Browsers promote elements when compositor animations start. Add hints only where the first frame matters or a trace shows a hitch, and remove them afterwards.