NEAT

Animated 3D backgrounds for websites

Neat renders a displaced 3D surface in WebGL and animates it behind your page. It is one canvas element and a config object, it does not require a 3D library, and it does not need you to know anything about shaders.

Open the editor 25 presets

Why it looks three-dimensional

Most "3D" backgrounds on the web are 2D images with a parallax offset. This is not that. A subdivided plane geometry is displaced along its normal by a Perlin noise field, then lit — so the light and shadow you see follow actual geometry, and the surface occludes itself as it folds.

That is also why the wave amplitude control has such a large effect. At 0 the plane stays flat and you get a conventional 2D mesh gradient; as you raise it, the surface starts folding and the shading appears with it.

Adding it to a site

Position a canvas behind your content and hand it to the constructor. Framework-agnostic, because it only needs a DOM node:

import { NeatGradient } from "@firecms/neat";

const gradient = new NeatGradient({
  ref: document.getElementById("gradient"),
  colors: [
    { color: "#130437", enabled: true },
    { color: "#B34BD0", enabled: true },
    { color: "#210751", enabled: true },
    { color: "#3511A5", enabled: true },
  ],
  speed: 4,
  waveAmplitude: 0,
  colorBrightness: 1.95,
  grainScale: 6,
});

// Clean up on unmount
// gradient.destroy();

In React

Create it in an effect and destroy it in the cleanup. The common mistake is letting a re-render construct a second instance on the same canvas:

import { useEffect, useRef } from "react";
import { NeatGradient } from "@firecms/neat";

export function GradientBackground() {
  const canvasRef = useRef(null);

  useEffect(() => {
    const gradient = new NeatGradient({
      ref: canvasRef.current,
      colors: [
        { color: "#130437", enabled: true },
        { color: "#B34BD0", enabled: true },
        { color: "#3511A5", enabled: true },
      ],
      speed: 4,
      waveAmplitude: 0,
    });
    return () => gradient.destroy();
  }, []);

  return (
    <canvas
      ref={canvasRef}
      style={{ position: "fixed", inset: 0, width: "100%", height: "100%", zIndex: -1 }}
    />
  );
}

Reacting to scroll

Setting yOffset advances the noise field, so tying it to scroll position makes the surface evolve as the reader moves down the page rather than only with time:

window.addEventListener("scroll", () => {
  gradient.yOffset = window.scrollY;
}, { passive: true });

Keeping it usable

A full-viewport animated background is easy to get wrong. Three things worth holding to:

  • Contrast first. Check your body text against the brightest and darkest frames the gradient produces, not just the one on screen when you happened to look. Dark presets with a bright colour in the palette are the usual offenders.
  • Honour prefers-reduced-motion. Set speed to 0 rather than removing the gradient — a still one still looks intentional.
  • Have a fallback. WebGL can be unavailable or blocked. A CSS gradient as the canvas element's own background costs nothing and means the page never renders bare.

Questions

How do I add an animated 3D background to my website?

Install @firecms/neat, add a canvas element positioned behind your content, and pass it to the NeatGradient constructor along with a config object. It works with React, Vue, Svelte or plain JavaScript because it only needs a DOM node.

Does an animated 3D background hurt page speed?

The package is around 17 KB gzipped with no dependencies, and rendering runs on the GPU rather than the main thread, so it does not block interaction. The real cost is GPU time on low-end devices — export a still PNG or a short video loop if that is a concern for your audience.

Does it work on mobile?

Yes, on any device with WebGL support, which is effectively every current phone. Lower the wave amplitude and speed on small screens if you want to be conservative about battery.

Do I need Three.js?

No. Neat has no dependencies and talks to WebGL directly.

Keep reading