05단어마다 차오르는 문장
문장의 단어가 스크롤 위치를 따라 하나씩 선명해지는, 애플 소개 문장에서 볼 수 있는 효과입니다.
쓰임랜딩 첫 문장, 브랜드 선언 문구
animation-range 겹침view()글자색만

<main class="stage">
<div class="wf">
<div class="wf__scroll">
<div class="wf__spacer"></div>
<p class="wf__line">
<span class="wf__w is-demo">스크롤이</span>
<span class="wf__w is-demo">닿는</span>
<span class="wf__w is-demo">곳마다</span><br>
<span class="wf__w is-demo">한</span>
<span class="wf__w is-demo">단어씩</span>
<span class="wf__w is-demo">선명해집니다</span>
</p>
<div class="wf__spacer"></div>
</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
@include stage($stage-paper);
.wf { width: min(400px, 88vw); }
.wf__scroll {
height: clamp(150px, 62vh, 216px);
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
}
.wf__spacer { height: 32%; }
.wf__line {
margin: 0;
padding: 0 $sp-4;
text-align: center;
font: 700 clamp(24px, 6vw, 34px)/1.5 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: -.01em;
}
// ── 실제 동작: 단어마다 자기 자신의 view() 타임라인. delay 가 아니라
// 지오메트리(단어가 각자 제 구간을 지나가는 순서)가 저절로 스태거를 만든다 ──
.wf__w { color: rgba(23, 20, 26, .16); }
@supports (animation-timeline: view()) {
.wf__w:not(.is-demo) {
animation: wf-in linear both;
animation-timeline: view();
animation-range: entry 0% cover 42%;
}
}
@supports not (animation-timeline: view()) {
// 미지원 브라우저(Firefox·Safari): 문장 전체가 처음부터 선명하다
.wf__w:not(.is-demo) { color: $subject-ink; }
}
@keyframes wf-in {
from { color: rgba(23, 20, 26, .16); }
to { color: $subject-ink; }
}
// 데모 루프: 실제 스크롤 대신 단어가 시간 차로 밝아지는 걸 흉내(캡처용)
.wf__w.is-demo { animation: wf-loop $duration linear infinite; }
.wf__w.is-demo:nth-of-type(1) { animation-delay: 0ms; }
.wf__w.is-demo:nth-of-type(2) { animation-delay: 140ms; }
.wf__w.is-demo:nth-of-type(3) { animation-delay: 280ms; }
.wf__w.is-demo:nth-of-type(4) { animation-delay: 420ms; }
.wf__w.is-demo:nth-of-type(5) { animation-delay: 560ms; }
.wf__w.is-demo:nth-of-type(6) { animation-delay: 700ms; }
@keyframes wf-loop {
0% { color: rgba(23, 20, 26, .16); }
18% { color: $subject-ink; }
82% { color: $subject-ink; }
100% { color: rgba(23, 20, 26, .16); }
}
@media (prefers-reduced-motion: reduce) {
.wf__w.is-demo { animation: none !important; color: $subject-ink !important; }
.wf__scroll { scroll-behavior: auto; }
}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);
}
.wf {
width: min(400px, 88vw);
}
.wf__scroll {
height: clamp(150px, 62vh, 216px);
overflow-y: auto;
scrollbar-width: none;
}
.wf__scroll::-webkit-scrollbar {
display: none;
}
.wf__spacer {
height: 32%;
}
.wf__line {
margin: 0;
padding: 0 16px;
text-align: center;
font: 700 clamp(24px, 6vw, 34px)/1.5 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: -0.01em;
}
.wf__w {
color: rgba(23, 20, 26, 0.16);
}
@supports (animation-timeline: view()) {
.wf__w:not(.is-demo) {
animation: wf-in linear both;
animation-timeline: view();
animation-range: entry 0% cover 42%;
}
}
@supports not (animation-timeline: view()) {
.wf__w:not(.is-demo) {
color: #17141a;
}
}
@keyframes wf-in {
from {
color: rgba(23, 20, 26, 0.16);
}
to {
color: #17141a;
}
}
.wf__w.is-demo {
animation: wf-loop 2s linear infinite;
}
.wf__w.is-demo:nth-of-type(1) {
animation-delay: 0ms;
}
.wf__w.is-demo:nth-of-type(2) {
animation-delay: 140ms;
}
.wf__w.is-demo:nth-of-type(3) {
animation-delay: 280ms;
}
.wf__w.is-demo:nth-of-type(4) {
animation-delay: 420ms;
}
.wf__w.is-demo:nth-of-type(5) {
animation-delay: 560ms;
}
.wf__w.is-demo:nth-of-type(6) {
animation-delay: 700ms;
}
@keyframes wf-loop {
0% {
color: rgba(23, 20, 26, 0.16);
}
18% {
color: #17141a;
}
82% {
color: #17141a;
}
100% {
color: rgba(23, 20, 26, 0.16);
}
}
@media (prefers-reduced-motion: reduce) {
.wf__w.is-demo {
animation: none !important;
color: #17141a !important;
}
.wf__scroll {
scroll-behavior: auto;
}
}// 실제 동작: 단어마다 제 view() 타임라인이 붙는다(0줄). is-demo 클래스는
// 캡처용 미리보기 루프일 뿐 — 실제 스크롤에서는 각 단어가 제 구간을 지날 때 밝아진다.import type { CSSProperties } from "react";
import styles from "./WordFill.module.scss";
export type WordFillProps = {
duration?: number;
easing?: string;
color?: string;
words?: string[];
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function WordFill({
duration = 300,
easing = "linear",
color = "#2f6df6",
words = ["스크롤이", "닿는", "곳마다", "한", "단어씩", "선명해집니다"],
}: WordFillProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.wf} style={vars}>
{/* 단어마다 자기 자신의 view() 타임라인 — 지오메트리가 저절로 스태거를 만든다 */}
<div className={styles.scroll}>
<div className={styles.spacer} />
<p className={styles.line}>
{words.map((w, i) => (
<span key={i} className={styles.w}>
{w}
{i === 2 ? <br /> : " "}
</span>
))}
</p>
<div className={styles.spacer} />
</div>
</div>
);
}05 단어마다 차오르는 문장 — 측정 누적면적 4.778% · 움직인 프레임 17/23GODRICH 원작 · zip 에 SCSS·React 포함 · 홈페이지 스크롤 효과 9가지 — 애플 페이지처럼 되는 CSS
이 데모가 속한 글
홈페이지 스크롤 효과 9가지 — 애플 페이지처럼 되는 CSS홈페이지 스크롤 효과는 페이지를 내리는 손가락 하나에 화면이 반응하게 만드는 장치로, 애플 제품 페이지가 이 분야의 기준입니다. 자바스크립트 라이브러리 하나도 없이 CSS만으로 가로 패널·고정 장면 교체·단어 채움· 이 글의 데모 전부 보기


