01Falling snow
24 small circles start above the screen with nth-child-driven positions, sizes, and fall durations, drifting down via translateY and opacity.
Best forWinter promo hero backgrounds, year-end landing page headers
nth-child(n)translateY--i 지연

<main class="stage">
<div class="field" aria-hidden="true">
<span class="p" style="--i:0"></span>
<span class="p" style="--i:1"></span>
<span class="p" style="--i:2"></span>
<span class="p" style="--i:3"></span>
<span class="p" style="--i:4"></span>
<span class="p" style="--i:5"></span>
<span class="p" style="--i:6"></span>
<span class="p" style="--i:7"></span>
<span class="p" style="--i:8"></span>
<span class="p" style="--i:9"></span>
<span class="p" style="--i:10"></span>
<span class="p" style="--i:11"></span>
<span class="p" style="--i:12"></span>
<span class="p" style="--i:13"></span>
<span class="p" style="--i:14"></span>
<span class="p" style="--i:15"></span>
<span class="p" style="--i:16"></span>
<span class="p" style="--i:17"></span>
<span class="p" style="--i:18"></span>
<span class="p" style="--i:19"></span>
<span class="p" style="--i:20"></span>
<span class="p" style="--i:21"></span>
<span class="p" style="--i:22"></span>
<span class="p" style="--i:23"></span>
</div>
<p class="caption">Year-End Sale</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-loop * 3; // 6s — 눈송이가 위에서 아래까지 떨어지는 기준 시간
$easing: linear; // 일정한 속도로 낙하 (spring 은 눈에 안 맞는다)
$color: $subject-cream;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-ink);
.field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.caption {
position: relative; z-index: 1; margin: 0;
color: $subject-cream; font-weight: 700; font-size: clamp(16px, 5vw, 22px); line-height: 1; font-family: inherit;
text-shadow: 0 2px 8px rgba($stage-ink, .6);
}
.p {
position: absolute;
top: -8%;
left: 0;
width: 6px; height: 6px;
border-radius: 50%;
background: $color;
opacity: 0;
animation: fall $duration $easing infinite;
animation-delay: calc(var(--i) * -0.28s); // 음수 지연 — 처음부터 이미 흩어져 떨어지고 있는 것처럼
}
// nth-child 로 항목마다 다른 가로 위치·크기·속도 (SCSS 컴파일 시 고정, --i 는 지연에만 쓴다)
@for $i from 1 through 24 {
.p:nth-child(#{$i}) {
left: ((($i * 53) % 97)) * 1%;
$s: 4px + ($i % 4) * 2px;
width: $s; height: $s;
animation-duration: $duration + ($i % 5) * .6s;
}
}
@keyframes fall {
0% { transform: translateY(0) translateX(0); opacity: 0; }
8% { opacity: .9; }
92% { opacity: .85; }
100% { transform: translateY(340px) translateX(14px); opacity: 0; }
}
// ── 접근성: 움직임 줄이기 설정이면 제자리에 흐릿하게만 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .35; top: 40%; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.field {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.caption {
position: relative;
z-index: 1;
margin: 0;
color: #fff7e6;
font-weight: 700;
font-size: clamp(16px, 5vw, 22px);
line-height: 1;
font-family: inherit;
text-shadow: 0 2px 8px rgba(23, 20, 26, 0.6);
}
.p {
position: absolute;
top: -8%;
left: 0;
width: 6px;
height: 6px;
border-radius: 50%;
background: #fff7e6;
opacity: 0;
animation: fall 6s linear infinite;
animation-delay: calc(var(--i) * -0.28s);
}
.p:nth-child(1) {
left: 53%;
width: 6px;
height: 6px;
animation-duration: 6.6s;
}
.p:nth-child(2) {
left: 9%;
width: 8px;
height: 8px;
animation-duration: 7.2s;
}
.p:nth-child(3) {
left: 62%;
width: 10px;
height: 10px;
animation-duration: 7.8s;
}
.p:nth-child(4) {
left: 18%;
width: 4px;
height: 4px;
animation-duration: 8.4s;
}
.p:nth-child(5) {
left: 71%;
width: 6px;
height: 6px;
animation-duration: 6s;
}
.p:nth-child(6) {
left: 27%;
width: 8px;
height: 8px;
animation-duration: 6.6s;
}
.p:nth-child(7) {
left: 80%;
width: 10px;
height: 10px;
animation-duration: 7.2s;
}
.p:nth-child(8) {
left: 36%;
width: 4px;
height: 4px;
animation-duration: 7.8s;
}
.p:nth-child(9) {
left: 89%;
width: 6px;
height: 6px;
animation-duration: 8.4s;
}
.p:nth-child(10) {
left: 45%;
width: 8px;
height: 8px;
animation-duration: 6s;
}
.p:nth-child(11) {
left: 1%;
width: 10px;
height: 10px;
animation-duration: 6.6s;
}
.p:nth-child(12) {
left: 54%;
width: 4px;
height: 4px;
animation-duration: 7.2s;
}
.p:nth-child(13) {
left: 10%;
width: 6px;
height: 6px;
animation-duration: 7.8s;
}
.p:nth-child(14) {
left: 63%;
width: 8px;
height: 8px;
animation-duration: 8.4s;
}
.p:nth-child(15) {
left: 19%;
width: 10px;
height: 10px;
animation-duration: 6s;
}
.p:nth-child(16) {
left: 72%;
width: 4px;
height: 4px;
animation-duration: 6.6s;
}
.p:nth-child(17) {
left: 28%;
width: 6px;
height: 6px;
animation-duration: 7.2s;
}
.p:nth-child(18) {
left: 81%;
width: 8px;
height: 8px;
animation-duration: 7.8s;
}
.p:nth-child(19) {
left: 37%;
width: 10px;
height: 10px;
animation-duration: 8.4s;
}
.p:nth-child(20) {
left: 90%;
width: 4px;
height: 4px;
animation-duration: 6s;
}
.p:nth-child(21) {
left: 46%;
width: 6px;
height: 6px;
animation-duration: 6.6s;
}
.p:nth-child(22) {
left: 2%;
width: 8px;
height: 8px;
animation-duration: 7.2s;
}
.p:nth-child(23) {
left: 55%;
width: 10px;
height: 10px;
animation-duration: 7.8s;
}
.p:nth-child(24) {
left: 11%;
width: 4px;
height: 4px;
animation-duration: 8.4s;
}
@keyframes fall {
0% {
transform: translateY(0) translateX(0);
opacity: 0;
}
8% {
opacity: 0.9;
}
92% {
opacity: 0.85;
}
100% {
transform: translateY(340px) translateX(14px);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
opacity: 0.35;
top: 40%;
}
}import type { CSSProperties } from "react";
import styles from "./SnowFall.module.scss";
export type SnowFallProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 24;
export default function SnowFall({
duration = 6000,
easing = "linear",
color = "#fff7e6",
}: SnowFallProps) {
const vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
} as CSSProperties;
return (
<div className={styles.field} style={vars} aria-hidden="true">
{Array.from({ length: COUNT }, (_, i) => (
<span key={i} className={styles.p} style={{ "--i": i } as Vars} />
))}
</div>
);
}01 Falling snow — Measured area 11.553% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Particle Backgrounds — One Line, Copy-Paste
The post this demo lives in
9 CSS Particle Backgrounds — One Line, Copy-PasteA css particle background scatters small shapes across a stage and drifts each one along its own nth-child path with tra See all demos in this post


