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

9 CSS Particle Backgrounds — One Line, Copy-Paste

A css particle background scatters small shapes across a stage and drifts each one along its own nth-child path with transform and opacity alone, no JavaScript

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

The nine below are ordered by how their particles move, not by how flashy they look. The css falling snowflakes in 01, along with 04, 05, and 08, fall from above the stage. 02 and 03 hold their ground or slide sideways instead of falling. 06 climbs from the bottom up, and 07 wanders on a short, folding loop instead of committing to one direction. 09 closes the set as the odd one out — three wide bands sweep back and forth together instead of dozens of separate particles. Each code block below keeps only the rule that decides how many particles exist and how they're scattered; the complete file for all nine ships in the zip. When reaction matters more to you than direction, the CSS 3D cube carousel tilt set covers an axis that answers the cursor, and if it's a single button rather than a whole backdrop you need to move, the Tailwind v4 animation utilities are the shorter route.

01Falling snow

What a css snow effect background needs on first paint

24 small circles start above the frame, and nth-child hands each one a different left position, size, and fall duration before translateY and opacity carry it down. A single --i custom property multiplied by a negative offset staggers the delay, so all 24 loops are already mid-fall on first paint instead of starting stacked together.

Falling snow
Open in a new tab

01 · Falling snow — Measured area 11.553% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

nth-child(n)translateY--i 지연

Where a winter hero fits

It fits a winter promo hero or the top of a year-end landing page.

.p {
  position: absolute; top: -8%; left: 0;
  width: 6px; height: 6px; border-radius: 50%;
  animation: fall 6s linear infinite;
  animation-delay: calc(var(--i) * -0.28s);
}
@for $i from 1 through 24 {
  .p:nth-child(#{$i}) { left: #{($i * 53) % 97}%; }
}
@keyframes fall {
  0%   { transform: translateY(0); opacity: 0; }
  100% { transform: translateY(340px); opacity: 0; }
}

02Twinkling stars

24 dots, 24 schedules

24 dots sit scattered across the stage, each blinking between 0.2 and 1 opacity on its own schedule rather than all 24 sharing one visible clock. A glowing box-shadow keeps every dot reading as a point of light instead of a flat disc.

Twinkling stars
Open in a new tab

02 · Twinkling stars — Measured area 0.894% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

opacity 트윙클nth-child 지연1.5~3s

Behind a dark-mode hero

It works well behind a dark-mode landing hero or a night-themed event page.

.p {
  position: absolute; width: 5px; height: 5px; border-radius: 50%;
  box-shadow: 0 0 10px rgba(23, 20, 26, .55);
  animation: twinkle 1.8s ease-in-out infinite;
  animation-delay: calc(var(--i) * -0.31s);
}
@keyframes twinkle {
  0%, 100% { transform: scale(.7); opacity: .2; }
  50%      { transform: scale(1.3); opacity: 1; }
}

03SVG wave

The same artwork meeting itself

The same wave path is drawn twice, side by side, inside a layer set to 200% width, and translateX slides that whole layer by exactly half its own width on a loop. Because the seam between the two copies is the same artwork meeting itself, the restart never shows.

SVG wave
Open in a new tab

03 · SVG wave — Measured area 12.781% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

SVG pathtranslateX 루프레이어 시차

A footer divider or a travel backdrop

It suits a footer divider under a product section or the backdrop of a travel landing page.

.layer { position: absolute; left: 0; right: 0; bottom: 0; display: flex; width: 200%; }
.wave { width: 50%; flex: none; }
.layer--back  { animation: drift 6s linear infinite; }
.layer--front { animation: drift 3.9s linear infinite; }
@keyframes drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

04Falling rain

22 lines tilted 12 degrees

22 thin lines start tilted 12 degrees and fall diagonally in well under a second, with animation-delay staggering each line's start. The result reads as a bundle of streaks rather than one line repeating in sync.

Falling rain
Open in a new tab

04 · Falling rain — Measured area 12.851% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateY 대각선nth-child 속도차0.4~0.7s

A rainy-season promo

It fits a rainy-season promo background or the intro screen of a weather app.

.p {
  position: absolute; top: -12%; width: 2px; height: 22px;
  transform: rotate(12deg);
  animation: rain .6s linear infinite;
  animation-delay: calc(var(--i) * -0.07s);
}
@keyframes rain {
  0%   { transform: translateY(0) translateX(0) rotate(12deg); opacity: .7; }
  100% { transform: translateY(380px) translateX(60px) rotate(12deg); opacity: .3; }
}

05Falling confetti

Two rotation signs, one loop

26 small rectangles fall while spinning, and a second @for loop hands alternating pieces an opposite rotation sign, so roughly half turn clockwise and half turn counterclockwise. Every third rectangle switches to a second color instead of the whole batch sharing one.

Falling confetti
Open in a new tab

05 · Falling confetti — Measured area 23.881% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

rotate + translateYnth-child 색상1.8~2.6s

A payment-success screen

It suits a payment-success screen, a signup celebration, or an event-winner page.

.p { position: absolute; top: -10%; width: 7px; height: 11px; }
.p:nth-child(3n) { background: #2f6df6; }
@keyframes confetti {
  0%   { transform: translateY(0) rotate(0deg); opacity: 0; }
  100% { transform: translateY(360px) rotate(var(--spin, 360deg)); opacity: .85; }
}

06Rising bubbles

Scale growing on the way up

20 circles carry only a border and a faint fill, and translateY lifts each one from the bottom of the stage while scale grows then shrinks on the way up. The eased timing keeps the rise from reading as mechanical, closer to something actually pushing through liquid.

Rising bubbles
Open in a new tab

06 · Rising bubbles — Measured area 12.542% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateY 상승scale 맥동nth-child 크기

Behind a beverage or spa hero

It reads well behind a beverage or spa brand hero.

.p {
  position: absolute; bottom: -10%; width: 10px; height: 10px;
  border: 1.5px solid currentColor; border-radius: 50%;
  animation: rise 4s cubic-bezier(.2, .8, .2, 1) infinite;
}
@keyframes rise {
  0%   { transform: translateY(0) scale(.6); opacity: 0; }
  55%  { transform: translateY(-170px) scale(1.15); }
  100% { transform: translateY(-360px) scale(.8); opacity: 0; }
}

07Fireflies

A path that folds back on itself

16 soft dots glow through a blurred box-shadow and blink via opacity while a seven-stop keyframe changes their translate direction three separate times before folding back to the start. Because the path doubles back on itself instead of running in one line, each dot reads as wandering rather than falling.

Fireflies
Open in a new tab

07 · Fireflies — Measured area 1.859% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

opacity 명멸translate 방향전환nth-child 궤적

A nature brand or a mood portfolio

It fits a nature or camping brand background, or a mood-driven portfolio hero.

.p {
  position: absolute; width: 4px; height: 4px; border-radius: 50%;
  box-shadow: 0 0 12px rgba(255, 247, 230, .8);
  animation: drift 3.5s ease-in-out infinite;
}
@keyframes drift {
  0%  { transform: translate(0, 0); opacity: 0; }
  35% { transform: translate(14px, -10px); opacity: .9; }
  55% { transform: translate(-8px, -20px); opacity: .3; }
  100% { transform: translate(0, 0); opacity: 0; }
}

08Falling sakura petals

Three sideways nudges on the way down

18 small ellipses fall through a multi-stop keyframe that rotates and nudges each one sideways at three separate points on the way down, instead of dropping straight along one axis. That side-to-side nudge is what reads as wind rather than plain gravity.

Falling sakura petals
Open in a new tab

08 · Falling sakura petals — Measured area 9.572% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

rotate + swayanimation-delaynth-child 낙하시간

A spring promo or a wedding hero

It suits a spring promo background or a wedding and beauty brand hero.

.p { position: absolute; top: -10%; width: 10px; height: 6px; border-radius: 50%; }
@keyframes petal {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  30%  { transform: translate(18px, 110px) rotate(120deg); }
  60%  { transform: translate(-14px, 240px) rotate(240deg); }
  100% { transform: translate(10px, 380px) rotate(360deg); opacity: .3; }
}

09Drifting fog

One loop, three staggered starts

Three wide, blurred bands share one back-and-forth translateX loop running at the same speed, but animation-delay staggers when each band starts so all three overlap instead of moving as one flat sheet. Opacity breathes along the same cycle, which keeps the layer from ever settling into a static frame.

Drifting fog
Open in a new tab

09 · Drifting fog — Measured area 90.962% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateX 왕복opacity 숨쉬기animation-delay 위상

A mystery hero or an outdoor brand

It fits a mystery or horror content hero, or a mountain and outdoor brand background.

.p {
  position: absolute; left: -20%; width: 140%; height: 34%;
  filter: blur(6px);
  animation: driftx 2s linear infinite alternate;
}
.p:nth-child(2) { animation-delay: -0.66s; }
.p:nth-child(3) { animation-delay: -1.32s; }
@keyframes driftx {
  from { transform: translateX(-30%); opacity: .18; }
  to   { transform: translateX(30%);  opacity: .42; }
}

Where do these particles actually break?

Three separate bugs showed up while building this set, and each one hid in a different layer of the pipeline.

Percent resolves against the particle

The first sits inside translate() itself. Its percentage values resolve against the moving element's own box, not the stage it crosses. The pure css snow effect and four others — rain, confetti, bubbles, and petals — were first written with something like translateY(370%), on the assumption that a particle would cross most of the stage. A 6px circle moving 370% of its own 6px height only travels 6 to 22 pixels, and the field barely twitched: item 01's cumulative-area reading sat at 0.15% before anyone noticed. Switching all five to a fixed pixel distance — translateY(340px) for the snow — pushed that same reading to 11.61%.

Why a stronger blur reads as still

The second lives in item 09's blur. An early pass layered filter: blur(16px) on top of the opacity fade. The cumulative-area comparison between the first and last captured frame still read 94% — the band had, after all, ended up somewhere different — but the frame-to-frame movement count came back zero. Blur spreads a shape's edge across a wide, gradual transition instead of a hard line. The change between any two adjacent frames then fell under the pixel threshold this site's measurement script uses to call a frame "moved." Cutting the blur down and widening the band's own translateX range fixed it without touching the opacity curve.

Duration outrunning the capture window

The third is duration outrunning the capture window. That same fog band was first tuned to a 10-second loop, but this site's capture window runs only 2.0 seconds — a sliver that lands entirely inside ease-in-out's slow opening curve. So the reading came back as motionless. Matching the loop length to that 2.0-second window let the sweep register as motion again. Every one of the nine ships here with all three of those already fixed, inside an archive that only opens once fjv3dy5x goes in precisely as it's spelled on this page.

The overlong-loop trap that caught this fog band in 09 bites hardest on big sweeping shapes — the wave sections in the Memphis design pattern kit can measure as a still frame for the very same reason whenever their sweep outlasts the capture window.

Accessibility

Nothing newer than Baseline

All nine lean on nothing newer than transform, opacity, @keyframes, :nth-child(), and CSS custom properties, which have been Baseline and widely available for years — no vendor prefix or fallback branch needed anywhere in the set. The one exception worth flagging is item 09's filter: blur(), which reaches back further still, to Chrome 53, Safari 9.1, and Firefox 35.

What reduced motion leaves behind

Under prefers-reduced-motion, none of the nine simply vanishes — each one keeps a faint, still trace of itself instead. Snow, rain, confetti, and petals stop moving and settle at a low fixed opacity near the same spot they'd otherwise be crossing. Stars and fireflies hold at a fixed glow instead of blinking. The wave layers and the fog bands stop outright, freezing the last painted frame, while bubbles hold near the bottom of their rise instead of resetting to invisible.

@media (prefers-reduced-motion: reduce) {
  .p { animation: none; opacity: .35; }
}

Where to go next

MDN's prefers-reduced-motion page covers the operating-system settings that flip this media query on. Browse other CSS-only patterns from the CSS category hub, or see what this site does and doesn't sell on the about page.

FAQ

Can I use just one of these as a single css particle background, not the whole set?

Yes. Every item's SCSS lives in its own .p block with a particle count and keyframes that never reference any other item. Lifting one span loop and its matching HTML out of the zip is enough on its own. Nothing here assumes the other eight are also on the page. And since every scatter here is fixed at compile time, a page can carry several of these backgrounds side by side — the seeded background pattern generator pushes that same idea further, redrawing one fixed scatter from a single seed value.

Does a css background particle animation slow the page down with more particles?

Not by itself. Every one of the nine only animates transform and opacity, properties a browser can composite on their own layer without recalculating the page's layout each frame. So the span count — from 3 in the fog to 26 in the confetti set — matters far less than which properties move. The exception is item 09's blur, which is why it's spread across three wide bands rather than scaled up to two dozen small ones. A blur's repaint cost tracks the area it covers, not the number of elements carrying it.

Why do the falling petals look blue instead of pink?

This site's demo palette has no pink in it, so item 08 borrows $subject-blue rather than adding a color used nowhere else on the site. Once the zip is open, swapping it back to pink is a one-line change to the $color variable and nothing else in the file needs to move.

Enter the archive password

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