04Falling rain
22 thin lines fall diagonally via translate, with nth-child varying each one's tilt and speed slightly so they read as a bundle of rain streaks.
Best forRainy-season promo backgrounds, weather-app intro screens
translateY 대각선nth-child 속도차0.4~0.7s

<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>
</div>
<p class="caption">Rainy Season Sale</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-base * 2; // .6s 기준 — 빗줄기 한 번 떨어지는 시간
$easing: linear;
$color: $subject-blue;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-paper);
.field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.caption {
position: relative; z-index: 1; margin: 0;
color: $subject-ink; font-weight: 700; font-size: clamp(16px, 5vw, 22px); line-height: 1; font-family: inherit;
}
.p {
position: absolute;
top: -12%;
width: 2px; height: 22px;
border-radius: $r-xs;
background: linear-gradient(rgba($color, 0), $color);
opacity: .7;
transform: rotate(12deg);
animation: rain $duration $easing infinite;
animation-delay: calc(var(--i) * -0.07s);
}
@for $i from 1 through 22 {
.p:nth-child(#{$i}) {
left: ((($i * 37) % 96)) * 1%;
animation-duration: $duration + ($i % 4) * .1s;
}
}
@keyframes rain {
0% { transform: translateY(0) translateX(0) rotate(12deg); opacity: .7; }
100% { transform: translateY(380px) translateX(60px) rotate(12deg); opacity: .3; }
}
// ── 접근성: 대각선 낙하를 멈추고 흐린 선만 남긴다 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .25; top: 45%; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
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: #17141a;
font-weight: 700;
font-size: clamp(16px, 5vw, 22px);
line-height: 1;
font-family: inherit;
}
.p {
position: absolute;
top: -12%;
width: 2px;
height: 22px;
border-radius: 4px;
background: linear-gradient(rgba(47, 109, 246, 0), #2f6df6);
opacity: 0.7;
transform: rotate(12deg);
animation: rain 600ms linear infinite;
animation-delay: calc(var(--i) * -0.07s);
}
.p:nth-child(1) {
left: 37%;
animation-duration: 700ms;
}
.p:nth-child(2) {
left: 74%;
animation-duration: 800ms;
}
.p:nth-child(3) {
left: 15%;
animation-duration: 900ms;
}
.p:nth-child(4) {
left: 52%;
animation-duration: 600ms;
}
.p:nth-child(5) {
left: 89%;
animation-duration: 700ms;
}
.p:nth-child(6) {
left: 30%;
animation-duration: 800ms;
}
.p:nth-child(7) {
left: 67%;
animation-duration: 900ms;
}
.p:nth-child(8) {
left: 8%;
animation-duration: 600ms;
}
.p:nth-child(9) {
left: 45%;
animation-duration: 700ms;
}
.p:nth-child(10) {
left: 82%;
animation-duration: 800ms;
}
.p:nth-child(11) {
left: 23%;
animation-duration: 900ms;
}
.p:nth-child(12) {
left: 60%;
animation-duration: 600ms;
}
.p:nth-child(13) {
left: 1%;
animation-duration: 700ms;
}
.p:nth-child(14) {
left: 38%;
animation-duration: 800ms;
}
.p:nth-child(15) {
left: 75%;
animation-duration: 900ms;
}
.p:nth-child(16) {
left: 16%;
animation-duration: 600ms;
}
.p:nth-child(17) {
left: 53%;
animation-duration: 700ms;
}
.p:nth-child(18) {
left: 90%;
animation-duration: 800ms;
}
.p:nth-child(19) {
left: 31%;
animation-duration: 900ms;
}
.p:nth-child(20) {
left: 68%;
animation-duration: 600ms;
}
.p:nth-child(21) {
left: 9%;
animation-duration: 700ms;
}
.p:nth-child(22) {
left: 46%;
animation-duration: 800ms;
}
@keyframes rain {
0% {
transform: translateY(0) translateX(0) rotate(12deg);
opacity: 0.7;
}
100% {
transform: translateY(380px) translateX(60px) rotate(12deg);
opacity: 0.3;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
opacity: 0.25;
top: 45%;
}
}import type { CSSProperties } from "react";
import styles from "./RainLines.module.scss";
export type RainLinesProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 22;
export default function RainLines({
duration = 600,
easing = "linear",
color = "#2f6df6",
}: RainLinesProps) {
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>
);
}04 Falling rain — Measured area 12.851% · 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


