← Blog

Animated backgrounds in Remotion

Build animated backgrounds in Remotion — a drifting gradient, a moving grid and floating particles — that loop seamlessly and render deterministically, then put your scene on top.

Short answer: an animated background in Remotion is a full-frame <AbsoluteFill> whose style is computed from useCurrentFrame(). Drive it with an angle from 0 to 2π across the composition so it loops, use random(seed) instead of Math.random(), and layer your scene in a second <AbsoluteFill> above it.

1. A drifting gradient that loops

Two radial gradients orbiting in opposite directions. Because the angle makes exactly one full turn over the composition, the last frame matches the first:

import { AbsoluteFill, useCurrentFrame, useVideoConfig } from "remotion";

export const GradientBackground = () => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const angle = (frame / durationInFrames) * Math.PI * 2;

  const x = 50 + Math.cos(angle) * 25;
  const y = 50 + Math.sin(angle) * 25;

  return (
    <AbsoluteFill
      style={{
        background: [
          `radial-gradient(circle at ${x}% ${y}%, #7c3aed 0%, transparent 55%)`,
          `radial-gradient(circle at ${100 - x}% ${100 - y}%, #0ea5e9 0%, transparent 55%)`,
          "#09090b",
        ].join(", "),
      }}
    />
  );
};

Keep the movement slow — a quarter of the frame per loop. A background that moves as fast as the foreground competes with it.

2. A moving grid

A grid drawn with two linear-gradients, scrolled by backgroundPosition. To loop, the grid must travel a whole number of cells over the composition:

export const GridBackground = () => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();
  const cell = 64;
  const offset = (frame / durationInFrames) * cell * 2; // two cells per loop

  return (
    <AbsoluteFill
      style={{
        backgroundColor: "#ffffff",
        backgroundImage: [
          "linear-gradient(#e4e4e7 1px, transparent 1px)",
          "linear-gradient(90deg, #e4e4e7 1px, transparent 1px)",
        ].join(", "),
        backgroundSize: `${cell}px ${cell}px`,
        backgroundPosition: `0 ${offset}px`,
      }}
    />
  );
};

3. Floating particles, deterministic

Remotion renders frames in parallel across several browser tabs. Math.random() gives each one different numbers and the particles jump every frame. random() from remotion returns the same value for the same seed, every time:

import { AbsoluteFill, random, useCurrentFrame, useVideoConfig } from "remotion";

export const Particles = ({ count = 40 }: { count?: number }) => {
  const frame = useCurrentFrame();
  const { durationInFrames, height } = useVideoConfig();

  return (
    <AbsoluteFill style={{ backgroundColor: "#09090b" }}>
      {Array.from({ length: count }, (_, i) => {
        const x = random(`x-${i}`) * 100;
        const size = 2 + random(`s-${i}`) * 4;
        const start = random(`y-${i}`) * height;
        // One full trip up the frame per loop, wrapped.
        const y = (start - (frame / durationInFrames) * height + height) % height;
        return (
          <div
            key={i}
            style={{
              position: "absolute",
              left: `${x}%`,
              top: y,
              width: size,
              height: size,
              borderRadius: "50%",
              background: "white",
              opacity: 0.15 + random(`o-${i}`) * 0.35,
            }}
          />
        );
      })}
    </AbsoluteFill>
  );
};

4. Put the scene on top

Layer order is JSX order. The background goes first:

<AbsoluteFill>
  <GradientBackground />
  <AbsoluteFill>
    <TextReveal text="Ship the video, too" color="white" />
  </AbsoluteFill>
</AbsoluteFill>

Components that render a transparent background — every snapcn component does, on purpose — sit on any backdrop without a box around them.

Four things that break backgrounds

  • CSS animations. @keyframes run on wall-clock time; a render does not. They look fine in the Player and are frozen or wrong in the MP4.
  • var(--colour) in an animated value. Resolve colours to concrete values before you interpolate them, or the render can disagree with the preview.
  • Big filter: blur(). A 100px blur on a full 1080p frame is slow to render. A soft radial gradient gives the same look for almost nothing.
  • Banding. Dark gradients band in H.264. Keep some colour in the dark end rather than fading to pure black, or add a faint noise layer.

Ready-made backgrounds

snapcn is a library of product demo shots and does not ship a backgrounds category. For drop-in backgrounds, see Remotion Elements (it has one) or the libraries in the best Remotion component libraries. Then put a text animation or a device frame on top.

FAQ

How do you make an animated background in Remotion?

Render a full-frame AbsoluteFill whose style is computed from useCurrentFrame(), for example a radial gradient whose position moves along a circle, and place your scene in a second AbsoluteFill on top of it.

How do you make a Remotion background loop seamlessly?

Drive the motion with an angle that goes from 0 to 2π over the composition, frame / durationInFrames * 2 * Math.PI, and use sin and cos of it. The last frame then flows straight back into the first.

Can I use Math.random() in a Remotion background?

No. Remotion renders frames in parallel across workers, so Math.random() gives each frame different values and the background flickers. Use random(seed) from the remotion package, which returns the same value for the same seed every time.

Why does my CSS animated background not show up in the render?

CSS keyframes and transitions run on wall-clock time, and a Remotion render does not play in real time. Compute every animated value from the frame number instead.