svg-stroke-reveal

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Skill: svg-stroke-reveal

Skill: svg-stroke-reveal

Component vanilla HTML/CSS/JS — nét
<path>
trong SVG "vẽ" dần ra như có tay đang kẻ, theo % cuộn qua viewport hoặc một lần khi vào khung nhìn. Không dùng thư viện ngoài (không Anime.js, không GSAP).
基于原生 HTML/CSS/JS 的组件——让 SVG 中的
<path>
描边像被手绘一样,根据滚动穿过 viewport 的百分比,或在进入视口时一次性绘制出来。不使用外部库(不使用 Anime.js,也不使用 GSAP)。

WHAT

是什么

Purpose và context

目的与上下文

  • Purpose: nét
    <path>
    trong SVG "vẽ" dần ra theo % cuộn hoặc một lần khi vào viewport (stroke-dasharray/dashoffset), vanilla không lib ngoài; mặc định dùng bản gốc nguyên văn ở
    Rheinmir/uiux-asset
    .
  • Trigger (when to use):
    • Logo/chữ ký/icon cần hiệu ứng "vẽ dần" khi vào viewport (chạy 1 lần).
    • Biểu đồ đường, sơ đồ hành trình, đường route cần "vẽ ra" liên tục theo tiến trình cuộn (gắn với vị trí cuộn thật, không phải chạy 1 lần).
    • User nói "vẽ svg khi cuộn", "svg stroke draw", "logo vẽ dần", "path animation reveal", "stroke-dashoffset", hoặc /svg-stroke-reveal.
  • Non-goals: KHÔNG dùng cho 7 hiệu ứng cuộn chung (opacity/translate reveal, stagger text, highlight, nav co, scroll-to-top, scrollspy, parallax) — đó là
    skills/scroll-effects
    . Không dùng cho
    <text>
    chưa convert outline (xem Rules).
  • 目的: SVG 中的
    <path>
    描边根据滚动百分比,或在进入 viewport 时一次性“绘制”出来;使用原生实现,不依赖外部库;默认原封不动地使用
    Rheinmir/uiux-asset
    中的源文件。
  • 触发条件(何时使用):
    • Logo、签名或图标需要在进入 viewport 时呈现“逐步绘制”效果(仅运行一次)。
    • 折线图、路线图或 route 需要根据实际滚动进度连续“绘制”出来(与真实滚动位置绑定,而不是只运行一次)。
    • 用户说“滚动时绘制 SVG”“svg stroke draw”“logo 逐步绘制”“path animation reveal”“stroke-dashoffset”或
      /svg-stroke-reveal
      。
  • 非目标: 不用于 7 种通用滚动效果(opacity/translate reveal、文本 stagger、highlight、导航收缩、scroll-to-top、scrollspy、parallax)——这些属于
    skills/scroll-effects
    。不用于尚未转换为 outline 的
    <text>
    (见规则)。

Mental model

思维模型

[data-svgr] > <path>×N → getTotalLength() → dasharray = dashoffset = length (ẩn) → mode "once" (IO một lần + CSS transition) hoặc mode "scroll" (IO bật cờ + 1 listener rAF ghi dashoffset theo progress) → reduce-motion: dashoffset = 0 ngay
.
[data-svgr] > <path>×N → getTotalLength() → dasharray = dashoffset = length(隐藏)→ mode "once"(IO 一次 + CSS transition)或 mode "scroll"(IO 开启标志 + 1 个 rAF listener 记录基于进度的 dashoffset)→ reduce-motion:立即将 dashoffset = 0
。

Input và output contract

输入输出约定

FieldRequired?Ý nghĩa
InSVG thật của dự án (các
<path>
có nét)
có
<text>
phải convert outline trước
Inmode
once
hoặc
scroll
cólogo/chữ ký → once; biểu đồ/hành trình → scroll
In
design.md
của dự án
khôngcó thì đổi token màu theo nó
OutSVG gắn
[data-svgr]
+ CSS/JS trong dự án
cócopy nguyên file gốc hoặc adapt
assets/
Outbằng chứng verify
playwright-verify
cóảnh trước/giữa/sau (scroll) + once chỉ trigger 1 lần
字段必填?含义
输入项目中真实的 SVG(带描边的
<path>
)
是
<text>
必须先转换为 outline
输入mode
once
或
scroll
是logo/签名 → once;图表/路线 → scroll
输入项目的
design.md
否如有,则按其调整颜色 token
输出带有
[data-svgr]
的 SVG + 项目中的 CSS/JS
是原样复制源文件,或改编
assets/
中的实现
输出
playwright-verify
验证证据
是滚动前/中/后截图 + 确认 once 仅触发一次

Rules và capabilities

规则与能力

  • RULE-01 (MUST):
    getTotalLength()
    đo trên toạ độ nội tại của path, KHÔNG bị ảnh hưởng bởi CSS transform
    — nhưng nếu bạn scale phần tử chứa path bằng
    transform: scale(sx, sy)
    với
    sx != sy
    (scale KHÔNG ĐỀU hai trục) SAU khi đã set dasharray, tốc độ vẽ theo px sẽ méo lệch giữa hai trục vì animation vẫn chạy theo length gốc trong khi hiển thị đã bị bóp méo tỷ lệ. Luôn set kích thước qua
    viewBox
    +
    width
    /
    height
    đồng nhất tỉ lệ, tránh CSS scale lệch trục trên chính phần tử mang path.
  • RULE-02 (MUST):
    <text>
    không stroke-draw được
    — phải convert chữ/font sang outline
    <path>
    thật trước (Illustrator "Create Outlines", Figma "Flatten", hoặc tool convert font-to-path) rồi mới áp
    [data-svgr]
    . Áp thẳng lên
    <text>
    sẽ không có gì xảy ra vì
    <text>
    không có
    getTotalLength()
    .
  • RULE-03 (MUST): Mode "once" dùng CSS transition (mượt, rẻ CPU); mode "scroll" set dashoffset trực tiếp mỗi frame qua JS (bắt buộc để đồng bộ khít với vị trí cuộn) — đừng đổi ngược hai cách này.
  • RULE-04 (MUST): Một
    IntersectionObserver
    riêng cho mỗi mode (không gộp chung logic once/scroll vào 1 observer vì hành vi khác nhau: unobserve-1-lần vs toggle cờ liên tục) và một scroll listener rAF-throttle DUY NHẤT cho tất cả SVG mode "scroll" trên trang — không gắn nhiều listener
    scroll
    rời cho mỗi SVG.
  • RULE-05 (MUST): Verify bằng
    playwright-verify
    : chụp trước/giữa/sau khi cuộn qua SVG mode "scroll" để thấy dashoffset giảm dần, và assert SVG mode "once" chỉ trigger 1 lần dù cuộn ra vào nhiều lần.
  • Capabilities: đọc bản gốc (web/git) + asset skill +
    design.md
    ; ghi HTML/CSS/JS vào dự án đích; chạy trình duyệt headless để verify.
  • RULE-01(必须):
    getTotalLength()
    测量的是 path 的固有坐标系,不受 CSS transform 影响
    ——但是,如果在设置 dasharray 之后,再通过
    transform: scale(sx, sy)
    对承载 path 的容器进行缩放,且
    sx != sy
    (两轴缩放比例不一致),那么按 px 计算的绘制速度会在两个轴之间产生偏差,因为动画仍按原始 length 运行,而显示结果已被非均匀缩放。始终通过
    viewBox
    +
    width
    /
    height
    设置尺寸并保持比例一致,避免在直接承载 path 的元素上使用非等比 CSS scale。
  • RULE-02(必须):
    <text>
    无法进行 stroke-draw
    ——必须先将文字/字体转换为真实的 outline
    <path>
    (Illustrator "Create Outlines"、Figma "Flatten",或使用 font-to-path 转换工具),然后才能应用
    [data-svgr]
    。直接应用到
    <text>
    上不会产生任何效果,因为
    <text>
    没有
    getTotalLength()
    。
  • RULE-03(必须): mode "once" 使用 CSS transition(流畅、CPU 开销低);mode "scroll" 必须通过 JS 在每一帧直接设置 dashoffset(这是与实际滚动位置紧密同步的必要条件)——不要互换这两种实现方式。
  • RULE-04(必须): 每种 mode 使用独立的
    IntersectionObserver
    (不要把 once/scroll 逻辑合并到同一个 observer 中,因为两者行为不同:一次性 unobserve 与持续切换标志),并且页面上所有 mode 为 "scroll" 的 SVG 只能共用一个经过 rAF 节流的 scroll listener——不要为每个 SVG 分别绑定独立的
    scroll
    listener。
  • RULE-05(必须): 使用
    playwright-verify
    进行验证:滚动经过 mode "scroll" 的 SVG 时拍摄前/中/后截图,以显示 dashoffset 逐渐减小,并断言 mode "once" 即使多次滚出再滚入也只触发一次。
  • 能力:读取源文件(web/git)+ asset skill +
    design.md
    ;将 HTML/CSS/JS 写入目标项目;运行 headless 浏览器进行验证。

Failure boundaries

失败边界

  • Áp lên
    <text>
    chưa convert outline → không có gì xảy ra: blocked, yêu cầu convert sang
    <path>
    trước (RULE-02).
  • Phần tử mang path bị CSS scale lệch trục → tốc độ vẽ méo: failed, chuyển kích thước qua
    viewBox
    +
    width
    /
    height
    (RULE-01).
  • Assert verify đỏ (dashoffset không giảm dần, once trigger lại) → failed, sửa rồi verify lại.
  • 应用到尚未转换为 outline 的
    <text>
    → 不会产生任何效果:blocked,要求先转换为
    <path>
    (RULE-02)。
  • 承载 path 的元素使用非等比 CSS scale → 绘制速度发生偏差:failed,改用
    viewBox
    +
    width
    /
    height
    设置尺寸(RULE-01)。
  • 验证断言失败(dashoffset 没有逐渐减小,once 再次触发)→ failed,修复后重新验证。

HOW

如何执行

Main workflow

主流程

StepTypeInputsActionOutputs/exitFailure/next
W01judgmentSVG + yêu cầuChọn mode
once
hay
scroll
; kiểm SVG đã là
<path>
(không
<text>
)
mode
<text>
→ blocked tới khi convert
W02effectbảng Pen gốcMở link chạy, copy nguyên file
.html
(+
_assets/
) vào dự án, giữ comment ghi công
file gốc trong dự ánkhông được kéo lib ngoài → B01
W03effectfile + SVG thật +
design.md
Chỉ sửa nội dung/ảnh/màu (thay path
d=
)
SVG đã adapt—
W04deterministictrang chạy qua HTTPVerify bằng
playwright-verify
(RULE-05)
ảnh + assertđỏ → sửa, lặp W04
步骤类型输入操作输出/退出条件失败/下一步
W01判断SVG + 需求选择 mode
once
或
scroll
;检查 SVG 是否已为
<path>
(而非
<text>
)
mode
<text>
→ blocked,直到完成转换
W02效果源 Pen打开可运行的链接,将原始
.html
文件(以及
_assets/
)完整复制到项目中,并保留署名注释
项目中的源文件不允许引入外部库 → B01
W03效果文件 + 真实 SVG +
design.md
只修改内容/图片/颜色(替换 path 的
d=
)
已适配的 SVG—
W04确定性通过 HTTP 运行的页面使用
playwright-verify
验证(RULE-05)
截图 + 断言失败 → 修复并重复 W04

Branches

分支

IDKindGuardHành viSkip / failureRejoin
B01conditional_requireddự án KHÔNG được kéo lib ngoài (bản gốc)dùng bản rút gọn vanilla
assets/
(mục Reference — Assets), đọc rồi ADAPT
—W04
B02conditional_required
prefers-reduced-motion: reduce
strokeDashoffset = 0
ngay lúc init, bỏ observer/listener
—W04
ID类型守卫条件行为跳过/失败汇合点
B01条件必需项目不允许引入外部库(源文件)使用精简的原生
assets/
(见参考 — Assets),阅读后进行 ADAPT
—W04
B02条件必需
prefers-reduced-motion: reduce
初始化时立即设置
strokeDashoffset = 0
,移除 observer/listener
—W04

Validation và stopping

验证与停止条件

Assert tất định ở W04 (mode scroll: dashoffset giảm dần qua ảnh trước/giữa/sau; mode once: chỉ trigger 1 lần dù cuộn ra vào). Cần mắt: độ mượt, thứ tự nét. Dừng khi assert xanh.
在 W04 执行确定性断言(mode scroll:通过前/中/后截图确认 dashoffset 逐渐减小;mode once:即使多次滚出再滚入,也只触发一次)。还需人工检查:流畅度和描边顺序。断言通过后停止。

Examples

示例

  • Positive: "logo vẽ dần khi vào màn hình" →
    data-svgr="once"
    trên logo 4 path → vào viewport 30% thì vẽ lần lượt (stagger 160ms theo thứ tự DOM), cuộn ra vào lại không vẽ lại.
  • Boundary/failure: chữ ký là
    <text>
    font script → không có
    getTotalLength()
    , không vẽ gì → dừng, yêu cầu convert outline (Figma "Flatten") thành
    <path>
    rồi mới áp
    [data-svgr]
    .
  • 正向: “logo 进入屏幕时逐步绘制” → 在包含 4 个 path 的 logo 上设置
    data-svgr="once"
    → 进入 viewport 达 30% 时依次绘制(按 DOM 顺序以 160ms stagger)→ 滚出后再滚入不会重新绘制。
  • 边界/失败: 签名采用 script 字体的
    <text>
    → 没有
    getTotalLength()
    ,不会绘制 → 停止,要求先通过 Figma "Flatten" 转换为
    <path>
    ,然后再应用
    [data-svgr]
    。

Reference — Bản gốc — dùng TRƯỚC (copy nguyên file, đừng viết lại)

参考 — 源文件 — 优先使用(完整复制,不要重写)

Code gốc NGUYÊN VĂN của tác giả (CodePen public = MIT) nằm ở repo ngoài
Rheinmir/uiux-asset
— gallery chạy thật: https://rheinmir.github.io/uiux-asset/scroll-effects/ . Quy trình: mở link chạy để xem đúng hiệu ứng → copy nguyên file
.html
(+ thư mục
_assets/
nếu file trỏ tới) vào dự án → giữ comment ghi công dòng đầu → chỉ sửa nội dung/ảnh/màu. Chạy local qua HTTP, không
file://
.
git clone --depth 1 https://github.com/Rheinmir/uiux-asset
nếu cần cả bộ offline.
Pen gốc — tác giảLink
Interactive Cicada Genomics Landing Page — alyona-mysiurachạy · code
Scroll-Driven SVG Map Editorial Gallery — knyttnevechạy · code
Scroll-Driven Godzilla Walk-and-Destroy Animation — creativeoceanchạy · code
assets/
trong skill này chỉ là bản rút gọn vanilla tự viết — dùng khi dự án KHÔNG được kéo lib ngoài; mặc định dùng bản gốc ở trên.
作者的原始代码(CodePen public = MIT)位于外部仓库
Rheinmir/uiux-asset
——实际运行 gallery:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。 流程:打开可运行链接以查看正确效果 → 将原始
.html
文件(如果文件有引用,还包括
_assets/
目录)完整复制到项目 → 保留首行署名注释 → 只修改内容/图片/颜色。通过 HTTP 在本地运行,不要使用
file://
。 如需离线获取整套资源,可执行
git clone --depth 1 https://github.com/Rheinmir/uiux-asset
。
源 Pen — 作者链接
Interactive Cicada Genomics Landing Page — alyona-mysiura运行 · 代码
Scroll-Driven SVG Map Editorial Gallery — knyttneve运行 · 代码
Scroll-Driven Godzilla Walk-and-Destroy Animation — creativeocean运行 · 代码
本 skill 中的
assets/
只是手写的精简 vanilla 版本——仅在项目不允许引入外部库时使用;默认使用上述源文件。

Reference — Assets

参考 — Assets

assets/demo.html
+
assets/svg-stroke-reveal.css
+
assets/svg-stroke-reveal.js
— đọc rồi ADAPT vào SVG thật của dự án (thay path
d=
, đổi token màu theo
design.md
nếu có), không copy máy móc.
assets/demo.html
+
assets/svg-stroke-reveal.css
+
assets/svg-stroke-reveal.js
——阅读后 ADAPT 到项目真实 SVG 中(替换 path 的
d=
,如果有
design.md
则调整颜色 token),不要机械复制。

Reference — Cơ chế

参考 — 实现机制

  • Với MỖI
    <path>
    trong
    [data-svgr]
    : JS gọi
    path.getTotalLength()
    , set inline
    style.strokeDasharray = length
    và
    style.strokeDashoffset = length
    lúc init → nét ẩn hoàn toàn (dash đúng bằng chiều dài path, offset đẩy hết dash ra ngoài).
  • Mode
    data-svgr="once"
    (threshold 1 lần — logo/chữ ký):
    IntersectionObserver
    threshold 0.3, khi vào viewport → set
    strokeDashoffset = 0
    cho tất cả path con → CSS
    transition: stroke-dashoffset
    chạy 1 lần →
    unobserve
    ngay sau khi trigger, không lặp lại kể cả cuộn ra rồi cuộn lại.
  • Mode
    data-svgr="scroll"
    (progress liên tục — biểu đồ/hành trình):
    IntersectionObserver
    chỉ bật/tắt cờ "đang trong viewport"; khi đang bật, một scroll listener rAF-throttle (dùng chung 1 listener, không tạo nhiều) tính
    progress = (innerHeight - rect.top) / (innerHeight + rect.height)
    kẹp 0..1, rồi ghi trực tiếp
    strokeDashoffset = length * (1 - progress)
    mỗi frame — KHÔNG dùng CSS transition ở mode này (transition sẽ làm nét trễ nhịp so với vị trí cuộn thật).
  • Path phức tạp nhiều
    <path>
    con (logo nhiều nét, chữ ký) — mode "once" stagger bằng
    transition-delay: calc(var(--svgr-i) * 160ms)
    , JS gán
    --svgr-i
    theo THỨ TỰ path trong DOM (path đầu vẽ trước).
  • prefers-reduced-motion: reduce
    → JS set
    strokeDashoffset = 0
    ngay cho toàn bộ path lúc init, bỏ qua cả hai observer/listener — hiện full nét tức thì, không animate.
  • 对于
    [data-svgr]
    中的每个
    <path>
    :JS 在初始化时调用
    path.getTotalLength()
    ,设置内联
    style.strokeDasharray = length
    和
    style.strokeDashoffset = length
    → 描边完全隐藏(dash 与 path 长度相等,offset 将整个 dash 推出范围之外)。
  • Mode
    data-svgr="once"
    (单次 threshold——logo/签名):
    IntersectionObserver
    threshold 为 0.3,进入 viewport 后 → 将所有子 path 的
    strokeDashoffset = 0
    → CSS
    transition: stroke-dashoffset
    运行一次 → 触发后立即
    unobserve
    ,即使滚出后再滚入也不会重复。
  • Mode
    data-svgr="scroll"
    (连续进度——图表/路线):
    IntersectionObserver
    仅用于开启/关闭“当前位于 viewport 内”的标志;标志开启时,由一个经过 rAF 节流的 scroll listener(全局共用一个,不创建多个)计算
    progress = (innerHeight - rect.top) / (innerHeight + rect.height)
    ,并限制在 0..1 范围内,然后每帧直接写入
    strokeDashoffset = length * (1 - progress)
    ——此 mode 不使用 CSS transition(transition 会使描边进度落后于真实滚动位置)。
  • 对包含多个复杂子
    <path>
    的元素(多笔画 logo、签名)——mode "once" 使用
    transition-delay: calc(var(--svgr-i) * 160ms)
    实现 stagger,JS 按 path 在 DOM 中的顺序分配
    --svgr-i
    (第一个 path 先绘制)。
  • prefers-reduced-motion: reduce
    → JS 在初始化时立即为所有 path 设置
    strokeDashoffset = 0
    ,跳过两个 observer/listener——立即显示完整描边,不执行动画。

Origin

来源