09Thời gian đọc còn lại
Con số trong "còn 3 phút" giảm dần khi cuộn, trừ theo tỉ lệ đã đọc so với tổng.
Dùng choHeader bài viết dài, phiên bản web của newsletter
카운트다운분 단위scroll() 연동

<main class="stage">
<p class="time is-demo" role="status">
<span class="time__label">còn</span>
<span class="time__num" aria-hidden="true"></span>
<span class="time__label">phút</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 Thời gian đọc còn lại — Diện tích đo được 1.545% · khung hình chuyển động 12/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Scroll Progress Bar — không cần JS
Bài viết chứa demo này
9 CSS Scroll Progress Bar — không cần JSCSS scroll progress bar là thanh, vòng hay con số cho biết bạn đã đọc được bao nhiêu phần trang, tính trực tiếp từ vị tr Xem tất cả demo trong bài này


