06泡が上る
輪郭だけの円20個が画面下から translateY で上へ浮かび、途中で scale が伸び縮みする。
使いどころ飲料・酒類ブランドのヒーロー、ウォーターパーク・スパ紹介背景
translateY 상승scale 맥동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>
<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>
</div>
<p class="caption">爽やかな夏</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────
$duration: $dur-loop * 2; // 4s 기준
$easing: $ease-out;
$color: $subject-ink;
// ── 데모 무대 ───────────────────────────────
@include stage($stage-yellow);
.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;
bottom: -10%;
width: 10px; height: 10px;
border-radius: 50%;
border: 1.5px solid $color;
background: rgba($color, .1);
opacity: 0;
animation: rise $duration $easing infinite;
animation-delay: calc(var(--i) * -0.24s);
}
@for $i from 1 through 20 {
.p:nth-child(#{$i}) {
left: ((($i * 43) % 92)) * 1%;
$s: 6px + ($i % 5) * 2px;
width: $s; height: $s;
animation-duration: $duration + ($i % 4) * .5s;
}
}
@keyframes rise {
0% { transform: translateY(0) scale(.6); opacity: 0; }
12% { opacity: .8; }
55% { transform: translateY(-170px) scale(1.15); }
100% { transform: translateY(-360px) scale(.8); opacity: 0; }
}
// ── 접근성: 상승을 멈추고 제자리 거품만 남긴다 ────────────
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .4; bottom: 20%; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
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;
bottom: -10%;
width: 10px;
height: 10px;
border-radius: 50%;
border: 1.5px solid #17141a;
background: rgba(23, 20, 26, 0.1);
opacity: 0;
animation: rise 4s ease-out infinite;
animation-delay: calc(var(--i) * -0.24s);
}
.p:nth-child(1) {
left: 43%;
width: 8px;
height: 8px;
animation-duration: 4.5s;
}
.p:nth-child(2) {
left: 86%;
width: 10px;
height: 10px;
animation-duration: 5s;
}
.p:nth-child(3) {
left: 37%;
width: 12px;
height: 12px;
animation-duration: 5.5s;
}
.p:nth-child(4) {
left: 80%;
width: 14px;
height: 14px;
animation-duration: 4s;
}
.p:nth-child(5) {
left: 31%;
width: 6px;
height: 6px;
animation-duration: 4.5s;
}
.p:nth-child(6) {
left: 74%;
width: 8px;
height: 8px;
animation-duration: 5s;
}
.p:nth-child(7) {
left: 25%;
width: 10px;
height: 10px;
animation-duration: 5.5s;
}
.p:nth-child(8) {
left: 68%;
width: 12px;
height: 12px;
animation-duration: 4s;
}
.p:nth-child(9) {
left: 19%;
width: 14px;
height: 14px;
animation-duration: 4.5s;
}
.p:nth-child(10) {
left: 62%;
width: 6px;
height: 6px;
animation-duration: 5s;
}
.p:nth-child(11) {
left: 13%;
width: 8px;
height: 8px;
animation-duration: 5.5s;
}
.p:nth-child(12) {
left: 56%;
width: 10px;
height: 10px;
animation-duration: 4s;
}
.p:nth-child(13) {
left: 7%;
width: 12px;
height: 12px;
animation-duration: 4.5s;
}
.p:nth-child(14) {
left: 50%;
width: 14px;
height: 14px;
animation-duration: 5s;
}
.p:nth-child(15) {
left: 1%;
width: 6px;
height: 6px;
animation-duration: 5.5s;
}
.p:nth-child(16) {
left: 44%;
width: 8px;
height: 8px;
animation-duration: 4s;
}
.p:nth-child(17) {
left: 87%;
width: 10px;
height: 10px;
animation-duration: 4.5s;
}
.p:nth-child(18) {
left: 38%;
width: 12px;
height: 12px;
animation-duration: 5s;
}
.p:nth-child(19) {
left: 81%;
width: 14px;
height: 14px;
animation-duration: 5.5s;
}
.p:nth-child(20) {
left: 32%;
width: 6px;
height: 6px;
animation-duration: 4s;
}
@keyframes rise {
0% {
transform: translateY(0) scale(0.6);
opacity: 0;
}
12% {
opacity: 0.8;
}
55% {
transform: translateY(-170px) scale(1.15);
}
100% {
transform: translateY(-360px) scale(0.8);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.p {
animation: none;
opacity: 0.4;
bottom: 20%;
}
}import type { CSSProperties } from "react";
import styles from "./BubblesRise.module.scss";
export type BubblesRiseProps = {
/** 애니메이션 기준 시간(ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 피사체 색 */
color?: string;
};
type Vars = CSSProperties & { "--i"?: number };
const COUNT = 20;
export default function BubblesRise({
duration = 4000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#17141a",
}: BubblesRiseProps) {
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>
);
}06 泡が上る — 計測 累積面積 12.542% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · CSS 背景アニメーション 星 9選 — 一行でコピペOK
このデモが属する記事
CSS 背景アニメーション 星 9選 — 一行でコピペOKcss 背景アニメーション 星のような検索でたどり着く人が本当に探しているのは、雪や星、雨、紙吹雪、泡、蛍、花びら、霧のように小さな粒を画面全体にばらまいて動かす背景の作り方です。span 1個をnth-childで何十回も繰り返し、位置・ この記事のデモをすべて見る


