GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 CSS Background Animation — One Line, Copy-Paste

A css background animation is a decorative layer — a shifting gradient, a drifting grid, blurred shapes — that loops behind real content using only

Auto-plays · click a tile to jump to its section · all nine in one zip

The nine below move from the simplest trick to the most involved one. 01 shifts nothing but background-position under an oversized gradient. 02, 03, and 05 stack several blurred or morphing shapes and move each one on its own path with transform. 04, 07, and 08 tile a repeating pattern and slide it three different ways, so the eye keeps reading motion instead of a fixed image. 06 fakes grain by jumping background-position with steps() instead of easing it. 09 is the odd one out — it only orbits on its own here because the real version follows the cursor through pointermove. The grid above never waits for a click: watch it for a few seconds and every one of the nine has already looped once, sealed inside a stage that doesn't scroll. When the motion belongs to the words instead of the ground they sit on, the CSS text animation effects are that half, and for photos laid over the backdrop that have to answer the pointer, the CSS image hover effects pair up with it.

01Gradient shift

Where the css background animation loop starts

A gradient is painted at 220% of its own box in both directions, and only background-position slides between two edges of that oversized canvas. The color band appears to flow like water instead of jumping between fixed stops.

Where it fits

Reach for it on a hero section or a premium call-to-action button, anywhere a background needs to feel alive without asking the browser to redraw the gradient itself.

Gradient shift
Open in a new tab

01 · Gradient shift — Measured area 99.739% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

background-position220% 220%$ease-out
.stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(120deg, $stage-ink, $color, $subject-cream, $stage-ink);
  background-size: 220% 220%;
  animation: flow $duration $easing infinite;
}

@keyframes flow {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

02Mesh gradient drift

How it works

Three radial-gradient blobs in different colors sit on top of each other, each one blurred and moved along its own translate path with its own animation-delay. The timing never lines up, so the blobs keep re-blending into new mixes instead of pulsing in unison.

Where it fits

It reads as a SaaS landing hero or an app onboarding backdrop, the kind of soft moving color field that looks hand-tuned rather than templated.

Mesh gradient drift
Open in a new tab

02 · Mesh gradient drift — Measured area 52.819% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

radial-gradient 레이어transform: translate비동기 주기
.blob {
  position: absolute;
  width: 42%;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(12px);
  opacity: .9;
}

.b1 { background: radial-gradient($color, transparent 65%); top: -8%; left: -6%; animation: drift1 $duration $easing infinite; }
.b2 { background: radial-gradient($subject-cream, transparent 65%); bottom: -12%; right: -4%; animation: drift2 $duration $easing infinite; animation-delay: -.6s; }
.b3 { background: radial-gradient($stage-ink, transparent 65%); top: 28%; right: 22%; opacity: .65; animation: drift3 $duration $easing infinite; animation-delay: -1.2s; }

03Aurora blur

How it works

Two or three blurred, semi-transparent color shapes overlap at filter: blur(3px) and move only through transform — translate and rotate together, never blur or opacity. Each shape swings through a loop that looks like layered light rather than a spinning sticker.

Where it fits

Staggered animation-delay values keep the three from ever rotating in sync, which is what sells a dark hero background or the ambient lighting behind a premium landing section.

Aurora blur
Open in a new tab

03 · Aurora blur — Measured area 41.671% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

filter: blur반투명 레이어transform 회전
.aurora {
  position: absolute;
  width: 38%;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(3px);
  opacity: 1;
}

.a1 { background: radial-gradient($subject-cream 0%, $subject-cream 25%, transparent 55%); top: -8%; left: 8%; animation: spin1 $duration linear infinite; }

@keyframes spin1 { 0% { transform: translate(0, 0) rotate(0deg); } 50% { transform: translate(56px, 38px) rotate(180deg); } 100% { transform: translate(0, 0) rotate(360deg); } }

04Drifting grid lines

How it works

Two repeating-linear-gradient layers, one horizontal and one vertical, draw a plain grid, and a single linear-eased keyframe slides both layers' background-position across exactly one 32px cell. The loop has no visible seam where it restarts.

Where it fits

It fits a developer-tool hero or the empty state behind a data dashboard, anywhere a grid should read as infrastructure rather than decoration.

Drifting grid lines
Open in a new tab

04 · Drifting grid lines — Measured area 99.682% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

linear-gradient 2겹background-position대각선 이동
.stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba($color, .35) 1px, transparent 1px),
    linear-gradient(to bottom, rgba($color, .35) 1px, transparent 1px);
  background-size: 32px 32px, 32px 32px;
  animation: gridmove $duration $easing infinite;
}

@keyframes gridmove {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 32px 32px, 32px 32px; }
}

05Blob morph

How it works

Three blobs each keep one fixed, hand-picked border-radius value for their whole lifetime, and only opacity and transform (scale, rotate) cross-fade between them on a staggered schedule. The mass looks like it keeps reshaping itself without the browser ever recalculating a border-radius curve mid-frame.

Where it fits

It suits an about-page decoration or the illustration behind an empty state, somewhere a blob can breathe without competing with real content.

Blob morph
Open in a new tab

05 · Blob morph — Measured area 36.54% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

레이어 크로스페이드transform: scale·rotateopacity
.m1 { border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%; animation: morph1 $duration $easing infinite; }
.m2 { border-radius: 63% 37% 41% 59% / 55% 48% 52% 45%; opacity: 0; animation: morph2 $duration $easing infinite; }
.m3 { border-radius: 35% 65% 55% 45% / 60% 40% 60% 40%; opacity: 0; animation: morph3 $duration $easing infinite; }

@keyframes morph1 {
  0%   { opacity: 1; transform: scale(1) rotate(0deg); }
  28%  { opacity: 1; transform: scale(1.05) rotate(4deg); }
  38%  { opacity: 0; transform: scale(.94) rotate(8deg); }
  92%  { opacity: 0; }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

06Noise grain overlay

How it works

A single repeating-conic-gradient tile is painted at just 3 by 3 pixels and tiled edge to edge, then background-position jumps between frames through steps(40) instead of any smoothed easing, so the tiny wedges appear to crawl like real film grain rather than glide. It only ever shifts position, so nothing newer than plain conic-gradient support is needed; animating the gradient's own stop angles directly would call for a registered custom property instead, and @property only reached Chrome 85, Safari 16.4, and Firefox 128.

Where it fits

Drop it under a hero image or a premium card at low opacity for texture, not as a whole background on its own.

Noise grain overlay
Open in a new tab

06 · Noise grain overlay — Measured area 99.705% · moving frames 21/23Original by GODRICH · SCSS and React ship in the zip · See the password

repeating-conic-gradientsteps()background-position
.stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: repeating-conic-gradient(from 0deg, rgba($color, .55) 0deg 90deg, transparent 90deg 180deg);
  background-size: 3px 3px;
  opacity: .35;
  animation: grain $duration $easing infinite;
}

@keyframes grain {
  from { background-position: 0 0; }
  to   { background-position: 117px 69px; }
}

07Drifting dot pattern

How it works

A single radial-gradient dot is tiled into a 20px grid, and background-position drifts diagonally at a constant linear pace across exactly one tile's width and height. The pattern loops without ever showing where one tile ends and the next begins.

Where it fits

It sits well behind a card grid or a slide-deck background, quiet enough not to compete with whatever is printed on top of it.

Drifting dot pattern
Open in a new tab

07 · Drifting dot pattern — Measured area 24.738% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

radial-gradient 도트background-size 타일대각선 흐름
.stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, rgba($color, .6) 1.5px, transparent 1.5px);
  background-size: 20px 20px;
  animation: dotdrift $duration $easing infinite;
}

@keyframes dotdrift {
  from { background-position: 0 0; }
  to   { background-position: 20px 20px; }
}

08Diagonal stripe move

How it works

The stripes are drawn straight — a plain repeating-linear-gradient running at 90 degrees — inside a layer that is oversized and then rotated a full 45 degrees with transform. Only after that rotation does background-position slide the pre-rotation layer sideways by one stripe-and-gap width.

Where it fits

Building it this way, instead of tilting the gradient itself to 45deg and nudging its position along that angle, is the one real trap in this set — more on why below — and it is what makes the stripes read as marching across a sale banner or a processing-state background instead of standing still.

Diagonal stripe move
Open in a new tab

08 · Diagonal stripe move — Measured area 99.674% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

repeating-linear-gradient45degbackground-position
.stage::before {
  content: "";
  position: absolute;
  inset: -150% -150%;
  transform: rotate(45deg);
  background-image: repeating-linear-gradient(90deg, rgba($color, .18) 0 18px, transparent 18px 36px);
  animation: stripemove $duration $easing infinite;
}

@keyframes stripemove {
  from { background-position: 0 0; }
  to   { background-position: 36px 0; }
}

09Cursor spotlight background

How it works

A single blurred radial-gradient circle acts as the spotlight, and in this demo transform: translate alone carries it around a fixed elliptical path. On a live page the same .spot element swaps that scripted path for the real x and y a pointermove listener reads off the cursor, gated behind a matchMedia('(pointer: fine)') check so touch screens never try to track a pointer that isn't there.

Where it fits

It turns an interactive landing background or the accent lighting on a dark hero into something that visibly reacts to the one person looking at it.

Cursor spotlight background
Open in a new tab

09 · Cursor spotlight background — Measured area 34.851% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

radial-gradient 스포트라이트transform: translatepointermove 대응
.spot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 120px;
  height: 120px;
  margin: -60px 0 0 -60px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba($color, .55), transparent 70%);
  filter: blur(6px);
  pointer-events: none;
}

.spot.is-demo { animation: orbit $duration $easing infinite; }

@keyframes orbit {
  0%   { transform: translate(-90px, -40px); }
  25%  { transform: translate(90px, -50px); }
  50%  { transform: translate(70px, 60px); }
  75%  { transform: translate(-80px, 55px); }
  100% { transform: translate(-90px, -40px); }
}

Where it breaks — the trap

Item 08 is the one demo in this set that could not be built the obvious way.

Why the diagonal cancels out

The natural instinct is to tilt the repeating-linear-gradient itself to 45deg and then slide background-position along that same diagonal — but a gradient that repeats along an axis and a background-position shift along that identical axis cancel each other out completely. Move it by one full stripe-and-gap and the pattern lands exactly back on itself, so the browser paints something that looks frozen even while the animation is technically running every frame.

Rotate the layer instead

The fix in the demo is to keep the gradient itself perfectly plain — vertical stripes at 90deg, nothing tilted — and instead rotate the whole oversized layer 45 degrees with transform once, up front. background-position then only has to slide sideways in that layer's own pre-rotation coordinate space, which the 45-degree transform carries along for the ride. The stripes visibly march across the stage the way a sale banner needs them to.

What's in the archive

Grab the archive with every one of the nine fixes already wired in — the vertical-stripe trick from item 08 included — by typing bc82wnvk into the prompt exactly as it reads here, all lowercase, no extra characters added. Scaling 02 and 03's drifting color up to whole landing sections is what the aurora mesh gradient kit does.

Accessibility

What each one freezes on

None of the nine goes fully still under prefers-reduced-motion, but each settles on a different frozen frame instead of one blanket rule. 01, 03, 04, 06, 07, and 08 simply stop their animation and hold whatever background-position or transform they were last computing. For a gradient, a grid, or a stripe set, that still reads as a complete, legible background rather than a half-drawn one. 02's three blobs freeze mid-drift in their own separate spots rather than resetting to a single overlapping mass, so the mesh still looks intentional even standing still. 05 goes further and fixes .m2 and .m3 at opacity 0, leaving only the first blob's own border-radius shape visible. Three overlapping static blobs would read as clutter where one clean shape reads as calm.

The pointer-driven outlier

09 is the outlier. The demo's own automatic orbit stops, but on a live page the reduced-motion query never touches the pointermove listener at all, since that motion is a direct response to where a real cursor already is rather than a loop the page invented on its own. MDN documents prefers-reduced-motion and the operating-system toggles that turn it on.

@media (prefers-reduced-motion: reduce) {
  .stage::before,
  .blob, .aurora,
  .m1, .m2, .m3,
  .spot.is-demo {
    animation: none;
  }
}

Browse the other patterns on the CSS hub, and check the about page for the short version of what ships here and what stays out.

FAQ

Is there a css background animation generator built into this set?

Not in the point-and-click sense — every demo compiles from three SCSS variables, usually $duration, $easing, and $color, so changing those three already covers most of what a generator's sliders would let you drag. Swap the color token, stretch the duration, and the same nine patterns rebuild themselves into a different-looking set without touching a single selector.

Do these css background animation examples loop without a visible seam?

Yes, and that is the actual engineering behind most of them. 01, 04, 06, 07, and 08 all move background-position across exactly one full tile, gradient span, or stripe width, so the frame at 100% lines up pixel-for-pixel with the frame at 0% and the restart is invisible. 02, 03, 05, and 09 loop the same way through their transform paths instead — each keyframe's last stop matches its first — so nothing here relies on a hidden reset or a hard cut you would notice on a slow monitor. Filling the ground with drifting pieces rather than color is the route the particle backgrounds set takes.

Can I preview one of these css background animation examples without a build tool?

Yes — every pattern compiles from plain SCSS and three variables, so pasting one demo's block into CodePen or any other SCSS playground previews it with no bundler involved. What you get from the archive carries an MIT license, so whichever of the nine you keep — vanilla markup or the react/ folder — is yours to reuse without crediting this site back.

Enter the archive password

The password is inside this article. You will find it as you read.