03Text wipe
A headline wipes in from the left with a single clip-path.
Best forHero headlines
600ms위에서가운데서

<main class="stage">
<h1 class="headline is-demo">Text wipes in</h1>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개. 변형은 이것만 바꾼다 (값은 토큰에서 받는다) ──────
$duration: $dur-base; // 닦여 나오는 시간
$easing: $ease-spring; // 처음 빠르고 끝은 부드럽게
$color: $subject-cream; // 글자 색 (orange 무대라 cream)
// ── 데모 무대 (zip 에서 지워도 된다) ──────────────────────────
@include stage($stage-orange);
// ── 제목 ──────────────────────────────────────────────
.headline {
margin: 0;
font-weight: 800;
font-size: clamp(22px, 7vw, 34px);
line-height: 1.3;
font-family: inherit;
color: $color;
text-align: center;
// ── 실제 사용 코드: clip-path 하나로 왼쪽에서 오른쪽으로 닦인다 ──
// width·left 대신 clip-path 만 바뀌므로 레이아웃 재계산이 없다.
@supports (animation-timeline: view()) {
animation: wipe-in $easing both;
animation-timeline: view();
animation-range: entry 20% cover 60%;
}
@supports not (animation-timeline: view()) {
clip-path: inset(0 0 0 0);
}
}
@keyframes wipe-in {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
// ── 데모(.is-demo): 스크롤 대신 2초 루프로 흉내 ────────────
.headline.is-demo {
animation: wipe-loop $dur-loop $easing infinite;
}
@keyframes wipe-loop {
0% { clip-path: inset(0 100% 0 0); }
35% { clip-path: inset(0 0 0 0); }
75% { clip-path: inset(0 0 0 0); }
100% { clip-path: inset(0 100% 0 0); }
}
// ── 접근성: 움직임 줄이기 설정이면 즉시 다 보여주기만 ────────
@media (prefers-reduced-motion: reduce) {
.headline, .headline.is-demo { animation: none !important; clip-path: inset(0 0 0 0); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.headline {
margin: 0;
font-weight: 800;
font-size: clamp(22px, 7vw, 34px);
line-height: 1.3;
font-family: inherit;
color: #fff7e6;
text-align: center;
}
@supports (animation-timeline: view()) {
.headline {
animation: wipe-in cubic-bezier(0.2, 0.8, 0.2, 1) both;
animation-timeline: view();
animation-range: entry 20% cover 60%;
}
}
@supports not (animation-timeline: view()) {
.headline {
clip-path: inset(0 0 0 0);
}
}
@keyframes wipe-in {
from {
clip-path: inset(0 100% 0 0);
}
to {
clip-path: inset(0 0 0 0);
}
}
.headline.is-demo {
animation: wipe-loop 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes wipe-loop {
0% {
clip-path: inset(0 100% 0 0);
}
35% {
clip-path: inset(0 0 0 0);
}
75% {
clip-path: inset(0 0 0 0);
}
100% {
clip-path: inset(0 100% 0 0);
}
}
@media (prefers-reduced-motion: reduce) {
.headline, .headline.is-demo {
animation: none !important;
clip-path: inset(0 0 0 0);
}
}import type { CSSProperties } from "react";
import styles from "./TextWipe.module.scss";
export type TextWipeProps = {
children: string;
/** 닦여 나오는 시간 (기본 600ms) */
duration?: number;
/** easing (기본 cubic-bezier(.2,.8,.2,1)) */
easing?: string;
/** 글자 색 (기본 #17141a) */
color?: string;
className?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function TextWipe({
children,
duration = 600,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#17141a",
className = "",
}: TextWipeProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<h1 className={`${styles.headline} ${className}`} style={vars}>
{children}
</h1>
);
}03 Text wipe — Measured area 2.99% · moving frames 13/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


