03Gạch chân viết tay
Một path SVG cong hơi lệch được vẽ bằng stroke-dasharray đúng bằng chiều dài path, còn stroke-dashoffset chạy từ giá trị đó về 0 rồi về âm, khiến gạch chân hiện ra rồi biến mất như bút vừa lướt qua.
Dùng choTừ khoá trong trích dẫn bài blog, từ nhấn mạnh trên trang giới thiệu bản thân
SVG pathstroke-dashoffset600ms

<main class="stage">
<p class="quote">
“<span class="hl">Sự kiên trì</span> đánh bại kỹ năng.”
<svg class="hl__svg" viewBox="0 0 120 20" preserveAspectRatio="none" aria-hidden="true">
<path class="hl__path" d="M2 12 C 20 18, 45 6, 62 11 S 100 17, 118 9" />
</svg>
</p>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ─────────────────────────────
$duration: 600ms; // 손으로 한 획을 긋는 데 걸리는 시간
$easing: $ease-out;
$color: $subject-blue; // 밑줄 색
// ── 데모 무대 ───────────────────────────────
@include stage($stage-ink);
.quote {
position: relative;
display: inline-block;
margin: 0;
color: $subject-cream;
font-weight: 600;
font-size: clamp(17px, 4.8vw, 23px);
line-height: 1.7;
font-family: inherit;
text-align: center;
}
.hl {
position: relative;
color: $subject-cream;
}
// 인용구 바로 아래, 키워드 폭만큼 걸쳐지는 SVG 밑줄 — 살짝 삐뚤한 손글씨 곡선
.hl__svg {
position: absolute;
left: 6%;
right: 46%; // "꾸준함" 위치에 맞춘 대략적 폭 (데모 전용 좌표)
bottom: -6px;
height: 16px;
overflow: visible;
}
.hl__path {
fill: none;
stroke: $color;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 140;
stroke-dashoffset: 140;
animation: draw $duration $easing infinite, draw-thick $duration ease-in-out infinite;
animation-delay: .2s;
}
@keyframes draw {
0% { stroke-dashoffset: 140; }
35% { stroke-dashoffset: 0; }
80% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -140; }
}
// 한 겹: 그어지는 동안 펜 압력처럼 stroke-width 가 요동 — stroke-width 는 transform 이
// 아니라 draw(stroke-dashoffset) 와 병렬로 돌아도 서로 덮지 않는다
@keyframes draw-thick {
0% { stroke-width: 3px; }
25% { stroke-width: 4.5px; }
45% { stroke-width: 3.5px; }
65% { stroke-width: 4px; }
85% { stroke-width: 3.2px; }
100% { stroke-width: 3px; }
}
@media (prefers-reduced-motion: reduce) {
.hl__path { animation: none; stroke-dashoffset: 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);
}
.quote {
position: relative;
display: inline-block;
margin: 0;
color: #fff7e6;
font-weight: 600;
font-size: clamp(17px, 4.8vw, 23px);
line-height: 1.7;
font-family: inherit;
text-align: center;
}
.hl {
position: relative;
color: #fff7e6;
}
.hl__svg {
position: absolute;
left: 6%;
right: 46%;
bottom: -6px;
height: 16px;
overflow: visible;
}
.hl__path {
fill: none;
stroke: #2f6df6;
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 140;
stroke-dashoffset: 140;
animation: draw 600ms ease-out infinite, draw-thick 600ms ease-in-out infinite;
animation-delay: 0.2s;
}
@keyframes draw {
0% {
stroke-dashoffset: 140;
}
35% {
stroke-dashoffset: 0;
}
80% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: -140;
}
}
@keyframes draw-thick {
0% {
stroke-width: 3px;
}
25% {
stroke-width: 4.5px;
}
45% {
stroke-width: 3.5px;
}
65% {
stroke-width: 4px;
}
85% {
stroke-width: 3.2px;
}
100% {
stroke-width: 3px;
}
}
@media (prefers-reduced-motion: reduce) {
.hl__path {
animation: none;
stroke-dashoffset: 0;
}
}import type { CSSProperties, ReactNode } from "react";
import styles from "./HandwrittenUnderline.module.scss";
export type HandwrittenUnderlineProps = {
/** 밑줄이 걸리는 강조어 */
keyword: ReactNode;
/** 강조어 뒤 문장 */
after?: ReactNode;
/** 한 획을 긋는 데 걸리는 시간 (기본 600ms) */
duration?: number;
easing?: string;
/** 밑줄 색 */
color?: string;
};
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
export default function HandwrittenUnderline({
keyword,
after,
duration = 600,
easing = "ease-out",
color = "#2f6df6",
}: HandwrittenUnderlineProps) {
const vars: Vars = {
"--duration": `${duration}ms`,
"--easing": easing,
"--color": color,
};
return (
<p className={styles.quote} style={vars}>
“<span className={styles.hl}>{keyword}</span>{after}
<svg className={styles.svg} viewBox="0 0 120 20" preserveAspectRatio="none" aria-hidden="true">
<path className={styles.path} d="M2 12 C 20 18, 45 6, 62 11 S 100 17, 118 9" />
</svg>
</p>
);
}03 Gạch chân viết tay — Diện tích đo được 0.301% · khung hình chuyển động 21/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Text Highlight Animation: dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Text Highlight Animation: dán 1 dòng là chạyCSS text highlight animation là hiệu ứng CSS thuần giúp tô sáng, gạch chân hoặc gạch ngang một từ trong câu ngay khi tra Xem tất cả demo trong bài này


