Reduce Repaints from Animated CSS Properties
Unnecessary repaints during CSS animations degrade frame rates, consume excess battery, and introduce visible jank across devices. This article outlines the primary strategies web engineers use to eliminate redundant paint cycles, focusing on compositor-only properties, layer promotion techniques, CSS containment, and performance profiling workflows designed to maintain a consistent 60 or 120 frames per second.
Stick to Transform and Opacity
Browsers render updates through three sequential stages: Layout,
Paint, and Composite. Animating geometric properties such as
width, height, top,
left, or margin forces the browser to
recalculate element geometry (layout) and redraw pixels across affected
surfaces (paint). Animating visual properties like color or
background-color skips layout but still forces a repaint on
every frame.
To bypass both layout and paint entirely, animate only
transform (including translate,
scale, and rotate) and opacity.
These properties are handled entirely by the compositor thread on the
GPU, allowing visual changes to occur without requiring the CPU to
rasterize new pixels.
Promote Animated
Elements with will-change
When an element animates on the standard render tree, its movement can dirty the entire layer, forcing surrounding elements to repaint. Promoting the moving element to its own compositor layer isolates its visual changes.
Apply the will-change property to signal this
requirement to the browser in advance:
.animated-box {
will-change: transform;
}Use will-change deliberately and sparingly. Creating too
many compositor layers consumes substantial GPU memory and introduces
compositor overhead that can paradoxically reduce performance. Remove or
reset the property when an animation concludes.
Isolate DOM Subtrees
Using CSS contain
The contain property informs the browser that an
element's internal styling and layout do not impact the rest of the
document. Applying containment prevents repaint events inside a
container from triggering cascading repaints outside of it:
.card-container {
contain: paint;
}Using contain: paint ensures that descendant elements
cannot draw outside the container bounds. If an offscreen or clipped
child element repaints, the browser ignores rendering passes outside the
designated bounding box.
Avoid Inadvertent Layout Thrashing
CSS animations interacting with JavaScript-driven logic often trigger
unintentional repaints through layout thrashing. If script logic reads
layout metrics (such as getBoundingClientRect(),
offsetWidth, or scrollTop) immediately after
mutating styles, the browser is forced to run synchronous layout and
repaint passes mid-frame.
To prevent this, batch all DOM style mutations separately from DOM
read operations, or rely on requestAnimationFrame() to sync
visual updates with browser refresh cycles.
Audit Paint Rectangles Using Browser DevTools
Identifying rogue repaints requires continuous runtime profiling:
- Open DevTools in Chrome or Chromium-based browsers.
- Open the Rendering tab via the More tools menu.
- Enable Paint Flashing.
With Paint Flashing active, any repainted region flashes green on screen. A well-optimized animation should show zero green flashes during movement; the animated element should move smoothly across the screen without triggering any repaint borders.