05Chữ được tô đầy dần
Màu chữ tiêu đề được tô đầy từ trái theo tỉ lệ đã đọc, vẽ bằng background-clip: text.
Dùng choTiêu đề hero, nhấn mạnh tiêu đề bài viết
background-clip:textlinear-gradient두 색

<main class="stage">
<h2 class="headline is-demo">Tiêu đề được tô đầy theo mức đã đọc</h2>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$color: $subject-blue; // 읽은 구간 색
$dim: rgba(255, 255, 255, .32); // 안 읽은 구간 색
$duration: $dur-loop;
@include stage($stage-ink);
// 퍼센트라야 그라데이션 경계가 매끄럽게 보간된다
@property --p {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
.headline {
--p: 0%;
max-width: 80%;
margin: 0;
text-align: center;
font-weight: 800;
font-size: clamp(20px, 6vw, 28px);
line-height: 1.35;
font-family: inherit;
background-image: linear-gradient(90deg, $color var(--p), $dim var(--p));
background-clip: text;
-webkit-background-clip: text;
color: transparent;
// 실제 사용: 헤드라인 색이 스크롤 진행률만큼 왼쪽부터 채워진다
@supports (animation-timeline: scroll()) {
animation: fill-text linear both;
animation-timeline: scroll(root);
}
}
@keyframes fill-text { from { --p: 0%; } to { --p: 100%; } }
.headline.is-demo { animation: fill-text-loop $duration linear infinite; }
@keyframes fill-text-loop {
0% { --p: 0%; }
92% { --p: 100%; }
100% { --p: 100%; }
}
@media (prefers-reduced-motion: reduce) {
.headline, .headline.is-demo { animation: none !important; --p: 100%; }
}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 --p {
syntax: "<percentage>";
initial-value: 0%;
inherits: false;
}
.headline {
--p: 0%;
max-width: 80%;
margin: 0;
text-align: center;
font-weight: 800;
font-size: clamp(20px, 6vw, 28px);
line-height: 1.35;
font-family: inherit;
background-image: linear-gradient(90deg, #2f6df6 var(--p), rgba(255, 255, 255, 0.32) var(--p));
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
@supports (animation-timeline: scroll()) {
.headline {
animation: fill-text linear both;
animation-timeline: scroll(root);
}
}
@keyframes fill-text {
from {
--p: 0%;
}
to {
--p: 100%;
}
}
.headline.is-demo {
animation: fill-text-loop 2s linear infinite;
}
@keyframes fill-text-loop {
0% {
--p: 0%;
}
92% {
--p: 100%;
}
100% {
--p: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.headline, .headline.is-demo {
animation: none !important;
--p: 100%;
}
}import { useEffect, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import styles from "./TextFill.module.scss";
export type TextFillProps = {
children: ReactNode;
/** 읽은 구간 색 */
color?: string;
/** 안 읽은 구간 색 */
dim?: string;
};
type Vars = CSSProperties & { "--p"?: string; "--color"?: string; "--dim"?: string };
// 실제 쓰임: 헤드라인 색이 스크롤 진행률만큼 왼쪽부터 채워진다.
export default function TextFill({ children, color = "#2f6df6", dim = "rgba(255,255,255,.32)" }: TextFillProps) {
const [percent, setPercent] = useState(0);
useEffect(() => {
const onScroll = () => {
const max = document.documentElement.scrollHeight - window.innerHeight;
setPercent(max > 0 ? Math.min(100, (window.scrollY / max) * 100) : 0);
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const vars: Vars = { "--p": `${percent}%`, "--color": color, "--dim": dim };
return <h2 className={styles.headline} style={vars}>{children}</h2>;
}05 Chữ được tô đầy dần — Diện tích đo được 2.938% · khung hình chuyển động 23/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


