09읽은 시간
"남은 3분" 문구의 숫자가 스크롤할수록 줄어든다. 전체 분량에서 읽은 비율만큼 뺀다.
쓰임긴 아티클 헤더, 뉴스레터 웹 버전
카운트다운분 단위scroll() 연동

<main class="stage">
<p class="time is-demo" role="status">
<span class="time__label">남은</span>
<span class="time__num" aria-hidden="true"></span>
<span class="time__label">분</span>
</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$total: 12; // 전체 예상 분 (두 자릿수로 시작해야 자릿수가 바뀌는 순간도 보인다)
$color: $subject-cream; // 숫자 색
$duration: $dur-loop;
@include stage($stage-ink);
@property --min {
syntax: '<integer>';
initial-value: #{$total};
inherits: false;
}
.time {
display: flex;
align-items: baseline;
gap: $sp-2;
margin: 0;
color: rgba(255, 255, 255, .7);
font-weight: 600;
font-size: 16px;
line-height: 1;
font-family: inherit;
}
.time__num {
--min: #{$total};
counter-reset: min var(--min);
color: $color;
font-weight: 800;
font-size: clamp(34px, 11vw, 46px);
line-height: 1;
font-family: inherit;
font-variant-numeric: tabular-nums;
&::before { content: counter(min); }
// 실제 사용: 남은 분수 = 전체 분수 × (1 − 스크롤 진행률)
@supports (animation-timeline: scroll()) {
animation: count-down linear both;
animation-timeline: scroll(root);
}
}
@keyframes count-down { from { --min: #{$total}; } to { --min: 0; } }
.time.is-demo .time__num { animation: count-down-loop $duration linear infinite; }
@keyframes count-down-loop {
0% { --min: #{$total}; }
92% { --min: 0; }
100% { --min: 0; }
}
@media (prefers-reduced-motion: reduce) {
.time__num, .time.is-demo .time__num { animation: none !important; --min: 0; }
}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);
}
@property --min {
syntax: "<integer>";
initial-value: 12;
inherits: false;
}
.time {
display: flex;
align-items: baseline;
gap: 8px;
margin: 0;
color: rgba(255, 255, 255, 0.7);
font-weight: 600;
font-size: 16px;
line-height: 1;
font-family: inherit;
}
.time__num {
--min: 12;
counter-reset: min var(--min);
color: #fff7e6;
font-weight: 800;
font-size: clamp(34px, 11vw, 46px);
line-height: 1;
font-family: inherit;
font-variant-numeric: tabular-nums;
}
.time__num::before {
content: counter(min);
}
@supports (animation-timeline: scroll()) {
.time__num {
animation: count-down linear both;
animation-timeline: scroll(root);
}
}
@keyframes count-down {
from {
--min: 12;
}
to {
--min: 0;
}
}
.time.is-demo .time__num {
animation: count-down-loop 2s linear infinite;
}
@keyframes count-down-loop {
0% {
--min: 12;
}
92% {
--min: 0;
}
100% {
--min: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.time__num, .time.is-demo .time__num {
animation: none !important;
--min: 0;
}
}import { useEffect, useState } from "react";
import styles from "./ReadingTime.module.scss";
export type ReadingTimeProps = {
/** 전체 예상 분 */
totalMinutes: number;
/** 숫자 색 */
color?: string;
};
// 실제 쓰임: 남은 분 = 전체 분 × (1 − 스크롤 진행률).
export default function ReadingTime({ totalMinutes, color = "#fff7e6" }: ReadingTimeProps) {
const [left, setLeft] = useState(totalMinutes);
useEffect(() => {
const onScroll = () => {
const max = document.documentElement.scrollHeight - window.innerHeight;
const p = max > 0 ? Math.min(1, window.scrollY / max) : 0;
setLeft(Math.max(0, Math.round(totalMinutes * (1 - p))));
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, [totalMinutes]);
return (
<p className={styles.time} role="status">
<span>left</span>
<span className={styles.num} style={{ color }}>{left}</span>
<span>min</span>
</p>
);
}09 읽은 시간 — 측정 누적면적 1.545% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · CSS 프로그레스바 애니메이션 9종 — 스크롤 읽기 진행 표시
이 데모가 속한 글
CSS 프로그레스바 애니메이션 9종 — 스크롤 읽기 진행 표시CSS 프로그레스바 애니메이션은 지금까지 얼마나 읽었는지를 스크롤 위치만으로 계산해 보여주는 장치입니다. AOS·GSAP 없이, JS 0줄로 아홉 가지 형태를 모았습니다 — 상단 바, 원형 배지, 점 내비, 세로 바 이 글의 데모 전부 보기


