05Falling confetti
26 small rectangles fall while rotating to flutter, with nth-child alternating their color, fall duration, and spin direction.
Best forPayment or signup success celebration screens, event-winner pages
rotate + translateYnth-child 색상1.8~2.6s

<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>
<span class="p" style="--i:24"></span>
<span class="p" style="--i:25"></span>
</div>
<p class="caption">Welcome Aboard</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-enter * 4; // 2.4s 기준
$easing: linear;
$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;
}
.p {
position: absolute;
top: -10%;
width: 7px; height: 11px;
background: $color;
opacity: .95;
animation: confetti $duration $easing infinite;
animation-delay: calc(var(--i) * -0.2s);
}
// 3개마다 색을 cream ↔ blue 로 번갈아 섞는다 (팔레트 안에서만)
.p:nth-child(3n) { background: $subject-blue; }
@for $i from 1 through 26 {
.p:nth-child(#{$i}) {
left: ((($i * 31) % 94)) * 1%;
animation-duration: $duration + ($i % 5) * .3s;
}
}
@for $i from 1 through 13 {
.p:nth-child(#{$i * 2}) {
--spin: #{240deg + ($i % 6) * 60deg};
}
.p:nth-child(#{$i * 2 - 1}) {
--spin: #{-1 * (240deg + ($i % 6) * 60deg)};
}
}
@keyframes confetti {
0% { transform: translateY(0) rotate(0deg); opacity: 0; }
8% { opacity: .95; }
100% { transform: translateY(360px) rotate(var(--spin, 360deg)); opacity: .85; }
}
// ── 접근성: 회전·낙하를 멈추고 흐린 조각만 남긴다 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .3; top: 45%; }
}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;
}
.p {
position: absolute;
top: -10%;
width: 7px;
height: 11px;
background: #fff7e6;
opacity: 0.95;
animation: confetti 2400ms linear infinite;
animation-delay: calc(var(--i) * -0.2s);
}
.p:nth-child(3n) {
background: #2f6df6;
}
.p:nth-child(1) {
left: 31%;
animation-duration: 2700ms;
}
.p:nth-child(2) {
left: 62%;
animation-duration: 3000ms;
}
.p:nth-child(3) {
left: 93%;
animation-duration: 3300ms;
}
.p:nth-child(4) {
left: 30%;
animation-duration: 3600ms;
}
.p:nth-child(5) {
left: 61%;
animation-duration: 2400ms;
}
.p:nth-child(6) {
left: 92%;
animation-duration: 2700ms;
}
.p:nth-child(7) {
left: 29%;
animation-duration: 3000ms;
}
.p:nth-child(8) {
left: 60%;
animation-duration: 3300ms;
}
.p:nth-child(9) {
left: 91%;
animation-duration: 3600ms;
}
.p:nth-child(10) {
left: 28%;
animation-duration: 2400ms;
}
.p:nth-child(11) {
left: 59%;
animation-duration: 2700ms;
}
.p:nth-child(12) {
left: 90%;
animation-duration: 3000ms;
}
.p:nth-child(13) {
left: 27%;
animation-duration: 3300ms;
}
.p:nth-child(14) {
left: 58%;
animation-duration: 3600ms;
}
.p:nth-child(15) {
left: 89%;
animation-duration: 2400ms;
}
.p:nth-child(16) {
left: 26%;
animation-duration: 2700ms;
}
.p:nth-child(17) {
left: 57%;
animation-duration: 3000ms;
}
.p:nth-child(18) {
left: 88%;
animation-duration: 3300ms;
}
.p:nth-child(19) {
left: 25%;
animation-duration: 3600ms;
}
.p:nth-child(20) {
left: 56%;
animation-duration: 2400ms;
}
.p:nth-child(21) {
left: 87%;
animation-duration: 2700ms;
}
.p:nth-child(22) {
left: 24%;
animation-duration: 3000ms;
}
.p:nth-child(23) {
left: 55%;
animation-duration: 3300ms;
}
.p:nth-child(24) {
left: 86%;
animation-duration: 3600ms;
}
.p:nth-child(25) {
left: 23%;
animation-duration: 2400ms;
}
.p:nth-child(26) {
left: 54%;
animation-duration: 2700ms;
}
.p:nth-child(2) {
--spin: 300deg;
}
.p:nth-child(1) {
--spin: -300deg;
}
.p:nth-child(4) {
--spin: 360deg;
}
.p:nth-child(3) {
--spin: -360deg;
}
.p:nth-child(6) {
--spin: 420deg;
}
.p:nth-child(5) {
--spin: -420deg;
}
.p:nth-child(8) {
--spin: 480deg;
}
.p:nth-child(7) {
--spin: -480deg;
}
.p:nth-child(10) {
--spin: 540deg;
}
.p:nth-child(9) {
--spin: -540deg;
}
.p:nth-child(12) {
--spin: 240deg;
}
.p:nth-child(11) {
--spin: -240deg;
}
.p:nth-child(14) {
--spin: 300deg;
}
.p:nth-child(13) {
--spin: -300deg;
}
.p:nth-child(16) {
--spin: 360deg;
}
.p:nth-child(15) {
--spin: -360deg;
}
.p:nth-child(18) {
--spin: 420deg;
}
.p:nth-child(17) {
--spin: -420deg;
}
.p:nth-child(20) {
--spin: 480deg;
}
.p:nth-child(19) {
--spin: -480deg;
}
.p:nth-child(22) {
--spin: 540deg;
}
.p:nth-child(21) {
--spin: -540deg;
}
.p:nth-child(24) {
--spin: 240deg;
}
.p:nth-child(23) {
--spin: -240deg;
}
.p:nth-child(26) {
--spin: 300deg;
}
.p:nth-child(25) {
--spin: -300deg;
}
@keyframes confetti {
0% {
transform: translateY(0) rotate(0deg);
opacity: 0;
}
8% {
opacity: 0.95;
}
100% {
transform: translateY(360px) rotate(var(--spin, 360deg));
opacity: 0.85;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
opacity: 0.3;
top: 45%;
}
}import type { CSSProperties } from "react";
import styles from "./ConfettiFall.module.scss";
export type ConfettiFallProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 26;
export default function ConfettiFall({
duration = 2400,
easing = "linear",
color = "#fff7e6",
}: ConfettiFallProps) {
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>
);
}05 Falling confetti — Measured area 23.881% · 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


