Background video vs animated gradient: what a hero costs

Measured file sizes for hero background videos, why grain makes them huge, and when a live gradient wins.

Updated

The lightweight alternative to a hero background video is a gradient drawn in the browser. Plain CSS costs nothing to download. A live WebGL or WebGPU gradient costs a fixed amount of JavaScript, about 50 KB gzipped for gradients.sh, whatever the size, length or grain of the gradient. A video costs bytes for every pixel and every frame, and grain is the part that makes those bytes explode.

That doesn't make video wrong. A smooth gradient compresses well, and a careful encode can be smaller than the JavaScript. This post has the numbers we measured, and the cases where each one wins.

Made for launch day

What a hero video weighs

We took one gradient, the Verdure preset, cut a 10-second, 1920×1080 excerpt from its 1920×1200 preview video, and encoded it with ffmpeg 6.1 several ways. The grain rows add moving noise with ffmpeg's noise filter at three strengths, from barely visible to the grain you see in the demo above.

Encode (10 s excerpt, 1920×1080)File size
VP9 WebM, CRF 32, 30 fps, no grain0.22 MB
H.264 MP4, CRF 23, 30 fps, no grain1.09 MB
H.264 MP4, CRF 23, 60 fps, no grain1.23 MB
H.264 MP4, CRF 23, 30 fps, light grain1.87 MB
H.264 MP4, CRF 23, 30 fps, medium grain9.73 MB
H.264 MP4, CRF 23, 30 fps, visible grain74.4 MB

The H.264 commands, so you can run them on your own clip. master.mp4 is a 1920×1200 source at 60 fps; drop fps=30 for the 60 fps row:

# no grain
ffmpeg -i master.mp4 -t 10 -vf "crop=1920:1080,fps=30" \
  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p -an -movflags +faststart hero.mp4

# grain: noise strength 4 (light), 8 (medium) or 12 (visible), changing every frame
ffmpeg -i master.mp4 -t 10 -vf "crop=1920:1080,fps=30,noise=alls=12:allf=t" \
  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p -an hero-grain.mp4

Two things stand out.

A smooth gradient is cheap as video. Soft colour that drifts slowly is close to the easiest thing a codec can compress. Our own preset previews are 1920×1200 loops of 8 to 30 seconds, and the median file is 1.04 MB in AV1 and 0.68 MB in HEVC. They're encoded without grain on purpose: the site draws the grain on top in the browser.

Grain is not. Real film grain changes on every frame, and random noise compresses poorly, so keeping it takes far more data. Going from light to visible grain made the same 10 seconds 40 times bigger. Grainy gradients are popular right now, and they're exactly the gradients that should not ship as video.

Weights add up in practice. On 2026-10-13 we measured one gradient marketplace's homepage at 16 MB transferred, 12.8 MB of it video.

What a live gradient costs

A live gradient moves the cost from the network to the GPU. You download code once, and the browser draws every frame.

  • Download. About 50 KB of gzipped JavaScript for the first gradients.sh component and its engine, plus about 8 KB that loads later in browsers that draw with WebGPU. Each extra component adds a few KB. The size doesn't change with the canvas size, the loop length or the grain.
  • Grain. The shader draws grain per pixel, every frame, so it stays sharp at any size and adds no bytes.
  • First paint. The server renders the gradient's colours as a CSS background at its final size, so the hero has colour before any JavaScript runs, and the first GPU frame replaces it in place with no layout shift.
  • GPU work. The engine draws once per display frame and caps the canvas at 1.5× device pixels. Gradients off screen or in a hidden tab stop drawing. Under load, it lowers the internal resolution, then draws every other frame.
  • Theme and size. Colours can be CSS variables, so the gradient follows light and dark mode. A 4K screen and a phone get the same code and a sharp result.
  • Reduced motion. Visitors who ask for reduced motion see one still frame. A video only respects that if you write the script yourself.

Plain CSS is lighter still: a gradient that slides with one keyframe costs no JavaScript and no GPU shader. It can't do soft mesh shapes or real grain, though. Animated gradient backgrounds in Tailwind CSS compares the CSS approaches. CSS gradient vs WebGL gradient traces which CSS animations repaint every frame.

When video is the better choice

Be honest about where video wins:

  • The look isn't a gradient. Filmed footage, 3D renders, particles and anything photographic need video. A shader that recreates them is a project of its own.
  • There's no code. Email headers, slides, social posts, digital signage and most video editors play an MP4 and run no JavaScript.
  • Battery on a long page. Most devices decode H.264 on dedicated hardware. A full-screen shader runs on the GPU for as long as it's on screen. The engine stops drawing off screen, but a hero someone reads for a minute keeps drawing for that minute.
  • Exact pixels. A video looks the same everywhere. A shader can look slightly different between GPUs and between WebGPU and WebGL2.

Video has costs that don't show in the file size, too. 8-bit H.264, the codec every browser plays, can band on slow dark gradients. Our own previews use 10-bit AV1 and HEVC for that reason. iPhones in Low Power Mode don't autoplay video, so visitors see the poster. And a looping video only loops cleanly if the last frame leads back into the first; How seamless gradient loops work explains why that's harder than it sounds.

Which one to use

HeroUse
Two or three colours sliding slowlyCSS
Soft mesh, aurora or waves, no grainLive gradient, or a small VP9/AV1 video
Any visible grainLive gradient
Has to follow light and dark modeLive gradient
Footage, 3D, photographic textureVideo
Email, slides, social, signageVideo

Swap a hero video for a live gradient

Install a gradients.sh component with the shadcn CLI:

pnpm dlx shadcn@latest add https://gradients.sh/r/grain-gradient.json

Then replace the <video> with the component. It fills its box and renders children on top:

import { GrainGradient } from "@/components/grain-gradient";

<GrainGradient className="h-[480px]" grain={0.5}>
  {children}
</GrainGradient>;

grain goes from 0 to 1 and adds no bytes at any value. Five other free looks work the same way: Mesh Gradient, Aurora, Waves, Radial Glow and Linear Flow. To match a video you already have, tune a gradient in the editor until it looks right, then copy its install command. If you still need a video for somewhere code can't run, the editor exports the same gradient as an MP4.