09Sương mù trôi
3 dải mờ cùng tốc độ translateX qua lại nhưng lệch animation-delay nên chồng lên nhau, opacity cũng dao động theo để tạo lớp sương như đang thở.
Dùng choHero nội dung bí ẩn và kinh dị, nền thương hiệu núi và ngoài trời
translateX 왕복opacity 숨쉬기animation-delay 위상

<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>
</div>
<p class="caption">Bí Ẩn Sắp Hé Lộ</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-loop; // 2s 기준 — 캡처 구간과 맞춰야 한 번의 이동이 가득 잡힌다
$easing: linear; // 일정한 속도라야 프레임마다 이동량이 고르게 나온다 (ease 는 시작·끝이 너무 느려 측정에 안 걸렸다)
$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;
}
// 층마다 폭이 넓은 흐릿한 띠. gap 없이 절대 위치로만 겹친다
// blur 를 세게 주면 가장자리 전이 구간이 넓어져, 몇 px 이동해도 픽셀당 변화가 문턱(12)을 못 넘는다 —
// blur 를 줄이고 이동 폭을 크게 줘야 프레임마다 실제로 측정 가능한 변화가 된다 (실측)
.p {
position: absolute;
left: -20%;
width: 140%;
height: 34%;
border-radius: $r-card;
background: $color;
opacity: .3;
filter: blur(6px); // 정적. 애니메이션은 transform·opacity 만
animation: driftx $duration $easing infinite alternate; // 왕복이라 이음새가 필요 없다
}
.p:nth-child(1) { top: 8%; animation-delay: 0s; }
.p:nth-child(2) { top: 38%; animation-delay: $duration * -.33; }
.p:nth-child(3) { top: 64%; animation-delay: $duration * -.66; }
@keyframes driftx {
from { transform: translateX(-30%); opacity: .18; }
to { transform: translateX(30%); opacity: .42; }
}
// ── 접근성: 흐름을 멈추고 고정된 안개층만 남긴다 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; }
}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;
left: -20%;
width: 140%;
height: 34%;
border-radius: 16px;
background: #fff7e6;
opacity: 0.3;
filter: blur(6px);
animation: driftx 2s linear infinite alternate;
}
.p:nth-child(1) {
top: 8%;
animation-delay: 0s;
}
.p:nth-child(2) {
top: 38%;
animation-delay: -0.66s;
}
.p:nth-child(3) {
top: 64%;
animation-delay: -1.32s;
}
@keyframes driftx {
from {
transform: translateX(-30%);
opacity: 0.18;
}
to {
transform: translateX(30%);
opacity: 0.42;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
}
}import type { CSSProperties } from "react";
import styles from "./FogDrift.module.scss";
export type FogDriftProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 3;
export default function FogDrift({
duration = 2000,
easing = "linear",
color = "#fff7e6",
}: FogDriftProps) {
const vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
} as CSSProperties;
return (
<div className={styles.field} style={vars} aria-hidden="true">
<span className={styles.p} />
<span className={styles.p} />
<span className={styles.p} />
</div>
);
}09 Sương mù trôi — Diện tích đo được 90.962% · khung hình chuyển động 23/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Particle Backgrounds — dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Particle Backgrounds — dán 1 dòng là chạyCSS particle background là kỹ thuật rải hàng chục phần tử nhỏ — chấm, vạch, mảnh nhỏ — bằng CSS thuần rồi cho chúng tự c Xem tất cả demo trong bài này


