07Đom đóm
16 chấm mờ vừa nhấp nháy qua opacity vừa đổi hướng liên tục trong quãng ngắn bằng translate, vẽ ra một quỹ đạo ánh sáng bất quy tắc.
Dùng choNền thương hiệu thiên nhiên và cắm trại, hero portfolio cảm xúc
opacity 명멸translate 방향전환nth-child 궤적

<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>
</div>
<p class="caption">Cắm Trại Đêm Hè</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-loop * 1.75; // 3.5s 기준
$easing: ease-in-out;
$color: $subject-cream;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-orange);
.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;
width: 4px; height: 4px;
border-radius: 50%;
background: $color;
box-shadow: 0 0 12px rgba($color, .8);
opacity: 0;
animation: drift $duration $easing infinite;
animation-delay: calc(var(--i) * -0.37s);
}
@for $i from 1 through 16 {
.p:nth-child(#{$i}) {
left: ((($i * 29) % 90)) * 1%;
top: ((($i * 61) % 78)) * 1%;
animation-duration: $duration + ($i % 4) * .4s;
}
}
// 짧게 방향을 바꾸며 떠다니는 불규칙한 궤적 (transform 만)
@keyframes drift {
0% { transform: translate(0, 0); opacity: 0; }
20% { opacity: .9; }
35% { transform: translate(14px, -10px); }
55% { transform: translate(-8px, -20px); opacity: .3; }
70% { opacity: .85; }
85% { transform: translate(10px, -6px); }
100% { transform: translate(0, 0); opacity: 0; }
}
// ── 접근성: 깜빡임·이동을 멈추고 흐린 점만 남긴다 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .45; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
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;
width: 4px;
height: 4px;
border-radius: 50%;
background: #fff7e6;
box-shadow: 0 0 12px rgba(255, 247, 230, 0.8);
opacity: 0;
animation: drift 3.5s ease-in-out infinite;
animation-delay: calc(var(--i) * -0.37s);
}
.p:nth-child(1) {
left: 29%;
top: 61%;
animation-duration: 3.9s;
}
.p:nth-child(2) {
left: 58%;
top: 44%;
animation-duration: 4.3s;
}
.p:nth-child(3) {
left: 87%;
top: 27%;
animation-duration: 4.7s;
}
.p:nth-child(4) {
left: 26%;
top: 10%;
animation-duration: 3.5s;
}
.p:nth-child(5) {
left: 55%;
top: 71%;
animation-duration: 3.9s;
}
.p:nth-child(6) {
left: 84%;
top: 54%;
animation-duration: 4.3s;
}
.p:nth-child(7) {
left: 23%;
top: 37%;
animation-duration: 4.7s;
}
.p:nth-child(8) {
left: 52%;
top: 20%;
animation-duration: 3.5s;
}
.p:nth-child(9) {
left: 81%;
top: 3%;
animation-duration: 3.9s;
}
.p:nth-child(10) {
left: 20%;
top: 64%;
animation-duration: 4.3s;
}
.p:nth-child(11) {
left: 49%;
top: 47%;
animation-duration: 4.7s;
}
.p:nth-child(12) {
left: 78%;
top: 30%;
animation-duration: 3.5s;
}
.p:nth-child(13) {
left: 17%;
top: 13%;
animation-duration: 3.9s;
}
.p:nth-child(14) {
left: 46%;
top: 74%;
animation-duration: 4.3s;
}
.p:nth-child(15) {
left: 75%;
top: 57%;
animation-duration: 4.7s;
}
.p:nth-child(16) {
left: 14%;
top: 40%;
animation-duration: 3.5s;
}
@keyframes drift {
0% {
transform: translate(0, 0);
opacity: 0;
}
20% {
opacity: 0.9;
}
35% {
transform: translate(14px, -10px);
}
55% {
transform: translate(-8px, -20px);
opacity: 0.3;
}
70% {
opacity: 0.85;
}
85% {
transform: translate(10px, -6px);
}
100% {
transform: translate(0, 0);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
opacity: 0.45;
}
}import type { CSSProperties } from "react";
import styles from "./Fireflies.module.scss";
export type FirefliesProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 16;
export default function Fireflies({
duration = 3500,
easing = "ease-in-out",
color = "#fff7e6",
}: FirefliesProps) {
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>
);
}07 Đom đóm — Diện tích đo được 1.859% · 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


