05Reading progress
A top bar fills as you read, driven only by a scroll() timeline.
Best forLong articles, docs
3px굵게 6px하단

<main class="stage">
<div class="bar is-demo" aria-hidden="true"></div>
<article class="doc">
<p class="doc__line"></p>
<p class="doc__line"></p>
<p class="doc__line short"></p>
</article>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개. 변형은 이것만 바꾼다 (값은 토큰에서 받는다) ──────
$thickness: 3px; // 바 두께. 굵게 6px
$color: $subject-blue; // 바 색 (ink 무대라 blue)
$track: rgba(255, 255, 255, .12); // 안 읽은 구간 색
// ── 데모 무대 (zip 에서 지워도 된다) ──────────────────────────
html, body { margin: 0; overflow: hidden; }
.stage {
box-sizing: border-box;
position: relative;
min-height: 100vh;
margin: 0;
padding: 40px clamp(16px, 6vw, 32px) 0;
background: $stage-ink;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
// ── 진행 바: 문서 최상단에 고정, scroll() 타임라인만 쓴다 ─────
.bar {
position: fixed;
top: 0; left: 0;
width: 100%;
height: $thickness;
background: $color;
transform: scaleX(0);
transform-origin: left; // 규칙8: 진행바 left 채움
// ── 실제 사용 코드: 문서 전체 스크롤 진행률이 곧 바 길이다 ──
// view() 가 아니라 scroll() — "얼마나 들어왔나" 가 아니라 "얼마나 읽었나".
@supports (animation-timeline: scroll()) {
animation: fill-bar linear both;
animation-timeline: scroll(root);
}
@supports not (animation-timeline: scroll()) {
transform: scaleX(0);
}
}
@keyframes fill-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
// ── 데모(.is-demo): 스크롤 대신 2초 루프로 흉내 ────────────
.bar.is-demo {
animation: fill-loop $dur-loop linear infinite;
}
@keyframes fill-loop {
0% { transform: scaleX(0); }
90% { transform: scaleX(1); }
100% { transform: scaleX(1); }
}
// ── 배경: 문서처럼 보이는 자리 ─────────────────────────
.doc { max-width: 380px; color: #fff; }
.doc__line {
height: 12px;
margin: 0 0 14px;
border-radius: 4px;
background: $track;
&.short { width: 60%; }
}
// ── 접근성: 움직임 줄이기 설정이면 즉시 채워 보여주기만 ────────
@media (prefers-reduced-motion: reduce) {
.bar, .bar.is-demo { animation: none !important; transform: scaleX(1); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
position: relative;
min-height: 100vh;
margin: 0;
padding: 40px clamp(16px, 6vw, 32px) 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
}
.bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: #2f6df6;
transform: scaleX(0);
transform-origin: left;
}
@supports (animation-timeline: scroll()) {
.bar {
animation: fill-bar linear both;
animation-timeline: scroll(root);
}
}
@supports not (animation-timeline: scroll()) {
.bar {
transform: scaleX(0);
}
}
@keyframes fill-bar {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
.bar.is-demo {
animation: fill-loop 2s linear infinite;
}
@keyframes fill-loop {
0% {
transform: scaleX(0);
}
90% {
transform: scaleX(1);
}
100% {
transform: scaleX(1);
}
}
.doc {
max-width: 380px;
color: #fff;
}
.doc__line {
height: 12px;
margin: 0 0 14px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.12);
}
.doc__line.short {
width: 60%;
}
@media (prefers-reduced-motion: reduce) {
.bar, .bar.is-demo {
animation: none !important;
transform: scaleX(1);
}
}import type { CSSProperties } from "react";
import styles from "./ProgressBar.module.scss";
export type ProgressBarProps = {
/** 바 두께 (기본 3px) */
thickness?: number;
/** 바 색 (기본 노랑) */
color?: string;
className?: string;
};
type Vars = CSSProperties & { "--thickness"?: string; "--color"?: string };
export default function ProgressBar({
thickness = 3,
color = "#ffd23f",
className = "",
}: ProgressBarProps) {
const vars: Vars = { "--thickness": `${thickness}px`, "--color": color };
return <div className={`${styles.bar} ${className}`} style={vars} aria-hidden="true" />;
}05 Reading progress — Measured area 1.0% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Animation on Scroll Effects, No JavaScript
The post this demo lives in
9 CSS Animation on Scroll Effects, No JavaScriptA css animation on scroll ties motion to how far the page has scrolled, using CSS's own animation-timeline instead of a See all demos in this post


