CSS Background Performance: LCP, Image Formats, and Paint Cost
Backgrounds are quietly responsible for some of the worst performance regressions in modern web pages. Hero images that double the page weight. Animated gradients that pin a phone's GPU at 100%. Glassmorphism panels that drag scrolling into single-digit frame rates on lower-end laptops. None of these are unavoidable; they're just easy to ship without realizing the cost. This page collects the trade-offs you can reason about before opening DevTools, and the few things that actually need a profiler.
The Single Biggest Decision: Image vs. CSS
Almost every background performance question reduces to one earlier one — does this need to be a raster image at all? Solid colors, gradients, and many decorative patterns can be expressed in CSS. A gradient declared in a stylesheet ships as a few dozen bytes inside the CSS file you were already going to download. The same effect drawn into a 1920×1080 PNG can be a quarter-megabyte over the wire and forces an additional HTTP request.
Default to CSS for:
- Solid colors and tinted surfaces.
- Linear, radial, and conic gradients.
- Repeating patterns where the unit is small (stripes, dots, grids, checkerboards).
- Composite "noise + gradient" hero treatments — better as inline SVG or
repeating-conic-gradientthan as a JPEG.
Reach for an image when the background is genuinely photographic, when it's a logo or a complex illustration, or when the visual depends on detail that would take more CSS than a small image would weigh.
Image Format Trade-offs
For backgrounds that have to be raster images, format choice matters more than dimensions. The current useful options:
| Format | Strengths | Weaknesses | Use for |
|---|---|---|---|
| AVIF | Smallest at acceptable quality; HDR support; alpha | Slower to decode on older devices; not universal in old browsers | Hero photos where bytes saved > small decode hit |
| WebP | Wide support; good compression; alpha | Slightly larger than AVIF at the same quality | Default workhorse for photo backgrounds |
| JPEG (progressive) | Universal; good for big photographic gradients | No alpha; larger files than AVIF/WebP | Fallback only |
| PNG | Lossless; alpha | Huge for photographic content | Sharp edges, small textures, alpha-required UI |
| SVG | Resolution-independent; tiny for vector content; CSS-controllable | Bad for photos; can be CPU-heavy with thousands of nodes | Patterns, icons, geometric backgrounds |
Use image-set() or the <picture> equivalent for backgrounds so that browsers that support newer formats get them and older ones still see something:
.hero {
background-image:
image-set(
url('hero.avif') type('image/avif'),
url('hero.webp') type('image/webp'),
url('hero.jpg') type('image/jpeg')
);
background-size: cover;
background-position: center;
}
LCP and Background Images
Largest Contentful Paint penalizes background images more than most developers expect. The core issue: the browser doesn't know about a background image until the CSS for that element parses, which is later than an <img> tag's src would be discovered. On a hero with a background photo, the LCP element is often that photo, and it shows up after a measurable delay.
Three fixes, in order of impact:
- Use
<img>for hero photos when you can. If the only thing the background does is display a photo behind a text overlay, an absolutely-positioned<img>withobject-fit: coverbehaves identically and gets discovered earlier. The text overlay sits in a sibling element on top. - Preload the hero background. Add
<link rel="preload" as="image" href="hero.webp" imagesrcset="..." imagesizes="100vw">to the head. Combined withfetchpriority="high", this closes most of the gap. - Inline a small placeholder. Encode a 20×20 blurred version as a data URL and put it in the CSS. The browser paints something immediately while the full photo loads.
Paint Cost of Gradients, Filters, and Animations
Pure-CSS visuals are not free either. A handful of effects are particularly expensive on low-end devices:
filter: blur()over large surfaces. Cost grows with both the blurred area and the radius. A blurred glass panel covering a hero section can take 8–15 ms per frame on a mid-range phone.backdrop-filter. Same cost profile, plus it forces a new compositing layer. Use it on small UI elements (cards, modals), not whole sections.- Multiple stacked radial gradients. Three or more large radial gradients on the same element multiply paint work. Flatten them into a single pre-rendered SVG or PNG when you don't need the gradients to be dynamic.
- Animated
background-position. Triggers paint every frame. Replace withtransform: translate()on a child layer for the same visual without the paint.
What to Animate (and What Not To)
Browsers compose only two CSS properties cheaply: transform and opacity. Anything else triggers layout or paint on every animation frame. The practical rule for animated backgrounds:
- Want the background to drift? Animate
transform: translate()on a child element that contains the gradient. - Want the background to fade between two looks? Stack two layers and animate
opacity. - Want a pulsing radial gradient? Animate
opacityortransform: scale(); do not animate the gradient stops or thebackground-imagestring.
/* Cheap: composited */
.bg-drift {
position: absolute; inset: 0;
background: radial-gradient(circle at 30% 30%, #1a73e8 0%, transparent 60%);
animation: drift 18s linear infinite;
will-change: transform;
}
@keyframes drift {
to { transform: translate(8%, 5%); }
}
/* Expensive: triggers paint every frame */
.bg-bad {
background: linear-gradient(var(--angle), #1a73e8, #000);
animation: rotate-bad 18s linear infinite;
}
Mobile and Reduced-Data Behaviour
Two media queries are worth gating background work behind:
prefers-reduced-motion: reduce— turn off animation entirely, or switch to a static fallback. Required for accessibility and helps low-end devices that struggle with the motion anyway.prefers-reduced-data: reduce— supply a smaller image, or skip non-essential decorative backgrounds. Support is uneven, but the cost of adding the rule is zero.
@media (prefers-reduced-motion: reduce) {
.bg-drift { animation: none; }
}
@media (prefers-reduced-data: reduce) {
.hero { background-image: linear-gradient(135deg, #0a1530, #000); }
}
Decision Criteria
- Can this background be expressed in CSS? If yes, use CSS.
- If it must be an image, can it be SVG? If yes, use SVG.
- If it must be raster, ship AVIF first, WebP second, JPEG/PNG as fallback via
image-set(). - If the page's LCP candidate is a background photo, switch to
<img>+ overlay or preload the asset. - If the design needs animation, animate only
transformandopacity. - Test on a mid-range phone before shipping. The desktop will lie to you about how fast the page feels.
What to Measure
- Lighthouse Performance score, with a focus on LCP and Total Blocking Time. LCP regressions almost always trace back to background images or web fonts.
- The Performance panel's "Rendering" tab — turn on Paint Flashing to see which elements re-paint during scroll or animation.
- Real-user metrics through the Web Vitals JS library if you have analytics. Lab numbers can be misleading on background-heavy designs because they don't reflect the variety of devices in the wild.
For the underlying techniques this page is optimizing, see the background-image reference, animated backgrounds, parallax scrolling, gradient mesh, glassmorphism, and the background-size: cover guide. For accessibility implications of optimizations like dropping motion or reducing contrast on weak signals, see accessible dark backgrounds.