Animated Backgrounds Without the Performance Bill: Canvas, WebGL and CSS in React
The background is never just decoration
An animated background is the first thing a visitor sees and the last thing a developer thinks about. It goes in at the end of a sprint because the hero looked flat, someone pastes a shader from a demo, and it looks wonderful on a desktop with a dedicated GPU. Then the page ships, and on a mid range phone the fan of a laptop spins up, the scroll stutters, and the headline sitting on top of all that motion is hard to read.
None of that is a reason to avoid moving backgrounds. They give a page mood in a way a static gradient cannot, and when they react to the cursor they make a site feel alive. The trick is to treat them like any other feature with a cost. This guide walks through the three main ways to build them in React, what each one is good at, and the handful of habits that keep them cheap.
Three engines, three price tags
Almost every animated background you will find is built on one of three things, and the choice decides most of the performance story before you write a line of logic.
- CSS gradients and transforms. Blurred blobs that drift, gradients that breathe, grids that slide. The browser does the work on the compositor, so when you only animate
transformandopacitythe main thread stays free. This is the cheapest option and the one to reach for first. - Canvas 2D. Particles, ASCII fields, light beams, anything where you draw shapes or characters each frame. You get full control, but every frame runs JavaScript on the main thread, so the cost grows with the number of things you draw.
- WebGL shaders. Plasma, silk, noise fields, liquid light. The GPU computes every pixel in parallel, which makes rich effects possible, but you pay for every pixel on screen, and on weak hardware or headless browsers the effect may not render at all.
A good rule: start with CSS, move to canvas when you need per element logic, and reach for WebGL only when the look truly needs it. The Background Gradient Animation in today's catalog is a good example of how far CSS goes. It is five blurred radial gradients on looping keyframes plus one that follows the pointer, and it reads as a living mesh of colour without a single draw call.
Canvas: keep the hot loop boring
Canvas backgrounds fail in a predictable way. They start simple, then someone adds React state to track the mouse, and suddenly every pointer move triggers a render of the whole tree. The fix is to keep animation state out of React entirely. Store it in refs or plain variables inside a useEffect, run your own requestAnimationFrame loop, and let React do nothing but mount the canvas and clean up.
The ASCII Plasma background shows a few more habits worth copying:
- Precompute what does not change per pixel. Most of its sine waves depend only on the column, the row or their sum, so they are computed once per frame into small tables instead of once per cell. The visual result is identical and the loop does a fraction of the work.
- Batch by style. Setting
globalAlphaorfillStyleis not free. Grouping cells into a few brightness buckets and drawing each bucket in one pass avoids thousands of state changes. - Cap the device pixel ratio. Rendering at 3x on a phone triples the pixels for a difference few people notice behind a headline. Clamping to 2 is a sensible default.
- Stop when nobody is looking. Pause the loop when the tab is hidden, and consider an
IntersectionObserverso a hero that scrolled out of view stops drawing.
The companion ASCII Wake takes a different approach: the field is almost empty at rest and only the cells the cursor touches light up and decay. A quiet default like that is kinder to battery and to reading than a field that is always busy.
Uncode UI
Build with AI, not against it.
Over 1,400 React and Tailwind components that run live in the browser, with a prompt ready to paste into Claude, Cursor or v0. Plus a community of people building real sites with AI.
WebGL: you are paying per pixel
A fragment shader runs once for every pixel, every frame. That is why a shader that is smooth in a 400 pixel preview can struggle full screen on a 4K monitor. A few practical guards help a lot.
- Size the canvas to its container, not the window. Many shader snippets listen to
window.resizeand make the canvas as big as the viewport even when it sits inside a smaller section. AResizeObserveron the canvas itself keeps the drawing buffer honest. That is one of the changes we made to the Shader Background before adding it. - Always cancel the loop. A
requestAnimationFramethat is never cancelled keeps a WebGL context alive after the component unmounts. In a single page app that adds up quickly, and browsers limit how many contexts a page can hold. - Check compile and link status. When a shader fails to compile it fails silently and you get a black or blank rectangle. Logging the info log saves hours.
- Render below native resolution if needed. For soft effects like aurora or plasma, drawing at half resolution and letting CSS scale it up is often invisible and halves the cost.
Plan for the case where WebGL is not available at all. Put a solid colour or a CSS gradient behind the canvas so the hero still looks intentional if the context never starts.
Respect reduced motion without going blank
Some people get dizzy or distracted by large moving surfaces, and they tell the browser so with the prefers-reduced-motion setting. Honouring it does not mean removing the background. It means showing a still frame.
For canvas, draw one frame and skip the loop. For CSS, wrap keyframe animations in a prefers-reduced-motion: no-preference media query so they only run when motion is welcome. For pointer effects, simply do not attach the listeners. The page keeps its look, and the people who asked for calm get it. Both ASCII backgrounds in today's set already behave this way.
The text on top has to win
The most common failure of animated backgrounds is not speed, it is contrast. A headline that is readable over the dark part of a gradient can disappear when a bright blob drifts behind it. Check the worst moment, not the average one.
- Add a vignette or a soft radial overlay that darkens the area behind the text. The Holographic Beams background does this with a radial gradient to black, which keeps the centre calm while the edges glow.
- Keep the brightest colours away from where copy sits. A gradient anchored near the top, like the one in Animated Gradient Background, leaves the lower half quiet for a call to action.
- Give text a subtle shadow or a frosted panel when the background is busy. It costs nothing and buys a lot of legibility.
- Mark purely decorative canvases with
aria-hiddenso screen readers skip them.
Measure on the device you are worried about
A background that looks fine on your machine tells you very little. Open the performance panel, enable CPU throttling, and record a few seconds of idle time on the page. If the main thread is busy even when nobody touches anything, the background is too expensive. Check scrolling too, since a canvas that repaints the whole viewport can make scroll feel heavy.
Then try it on a real phone. Watch for warm devices, dropped frames when the page first loads, and battery drain over a few minutes. If the effect only works on high end hardware, consider swapping it for a simpler variant on small screens, or rendering a static poster image until the user interacts.
A short checklist before you ship
- Use CSS first, canvas when you need logic, WebGL when the look demands it.
- Keep animation state out of React and cancel every loop on unmount.
- Size canvases to their container and cap the pixel ratio.
- Pause when the tab is hidden or the section is off screen.
- Show a still frame for reduced motion, and a fallback colour behind WebGL.
- Check text contrast at the brightest moment of the animation.
All the backgrounds mentioned here are in the Uncode catalog, adapted for dark interfaces and ready to copy. If you work in Cursor, Claude Code or Lovable, you can also connect the catalog to your editor and ask for one by name, then tune the colours and speed for your own hero.
Uncode UI
Build with AI, not against it.
Over 1,400 React and Tailwind components that run live in the browser, with a prompt ready to paste into Claude, Cursor or v0. Plus a community of people building real sites with AI.