08Lộ chữ qua mặt nạ quét
Thu hẹp giá trị phải của clip-path: inset() từ 100% về 0% quét tiêu đề lộ dần từ trái sang phải, còn gạch chân cùng nhịp kéo dài theo bằng scaleX.
Dùng choHero đề cao typography, mở đầu landing portfolio·editorial
clip-path: inset()scaleX underlinesweep reveal

<main class="stage is-demo">
<div class="headline">
<h1 class="headline__mask">Type less. Ship more.</h1>
<span class="headline__underline" aria-hidden="true"></span>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // 2s — 리빌·홀드·리셋 한 바퀴
$easing: $ease-spring;
$color: $subject-ink;
@include stage($stage-paper);
.headline {
margin: 0;
box-sizing: border-box;
max-width: clamp(220px, 80vw, 380px);
text-align: center;
}
// clip-path 를 애니메이션 대상으로 쓴다 — blog-demo 규칙 1(transform·opacity 만)의 의도적 예외.
// 마스크 리빌(왼쪽→오른쪽으로 쓸려 나오는 등장)은 clip-path 없이는 표현이 안 된다 (사실.md 기록)
.headline__mask {
display: inline-block;
margin: 0;
color: $color;
font-weight: 800;
font-size: clamp(20px, 6.4vw, 30px);
line-height: 1.3;
font-family: inherit;
clip-path: inset(0 100% 0 0);
}
.headline__underline {
display: block;
margin: $sp-2 auto 0;
width: 64px;
height: 3px;
background: #c78da8;
transform: scaleX(0);
transform-origin: left center; // 규칙8: 언더라인 left 그어짐
}
.stage.is-demo .headline__mask { animation: reveal $duration $easing infinite; }
.stage.is-demo .headline__underline { animation: underline-grow $duration $easing infinite; }
@keyframes reveal {
0% { clip-path: inset(0 100% 0 0); }
40% { clip-path: inset(0 0% 0 0); }
85% { clip-path: inset(0 0% 0 0); }
100% { clip-path: inset(0 100% 0 0); }
}
@keyframes underline-grow {
0% { transform: scaleX(0); }
40% { transform: scaleX(1); }
85% { transform: scaleX(1); }
100% { transform: scaleX(0); }
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .headline__mask { animation: none; clip-path: inset(0 0% 0 0); }
.stage.is-demo .headline__underline { animation: none; transform: scaleX(1); }
}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);
}
.headline {
margin: 0;
box-sizing: border-box;
max-width: clamp(220px, 80vw, 380px);
text-align: center;
}
.headline__mask {
display: inline-block;
margin: 0;
color: #17141a;
font-weight: 800;
font-size: clamp(20px, 6.4vw, 30px);
line-height: 1.3;
font-family: inherit;
clip-path: inset(0 100% 0 0);
}
.headline__underline {
display: block;
margin: 8px auto 0;
width: 64px;
height: 3px;
background: #c78da8;
transform: scaleX(0);
transform-origin: left center;
}
.stage.is-demo .headline__mask {
animation: reveal 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.stage.is-demo .headline__underline {
animation: underline-grow 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes reveal {
0% {
clip-path: inset(0 100% 0 0);
}
40% {
clip-path: inset(0 0% 0 0);
}
85% {
clip-path: inset(0 0% 0 0);
}
100% {
clip-path: inset(0 100% 0 0);
}
}
@keyframes underline-grow {
0% {
transform: scaleX(0);
}
40% {
transform: scaleX(1);
}
85% {
transform: scaleX(1);
}
100% {
transform: scaleX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.stage.is-demo .headline__mask {
animation: none;
clip-path: inset(0 0% 0 0);
}
.stage.is-demo .headline__underline {
animation: none;
transform: scaleX(1);
}
}import type { CSSProperties } from "react";
import styles from "./SplitTextMaskReveal.module.scss";
export type SplitTextMaskRevealProps = {
text?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function SplitTextMaskReveal({
text = "Type less. Ship more.",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#17141a",
}: SplitTextMaskRevealProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.headline} style={vars}>
<h1 className={styles.mask}>{text}</h1>
<span className={styles.underline} aria-hidden="true" />
</div>
);
}08 Lộ chữ qua mặt nạ quét — Diện tích đo được 2.719% · khung hình chuyển động 12/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 hero section css animation — không cần JS
Bài viết chứa demo này
9 hero section css animation — không cần JSHero section là màn hình lớn đập vào mắt đầu tiên khi bạn vừa vào một trang web, nơi đặt tiêu đề cỡ đại và nút bấm dựng Xem tất cả demo trong bài này


