Files
hyperframes/skills/hyperframes-animation/rules/svg-path-draw.md
T
WaterrrForeverandClaude Fable 5 a6a9e2f89e feat(skills): anchored-connector rule + source-traceable visuals doctrine (#3354)
* feat(skills): anchored-connector rule + source-traceable visuals doctrine

Two advisory rules absorbed from a community-skill comparison study
(4-cell sandbox replay vs geekjourneyx/hyperframes-motion-director;
ideas only — no upstream text, the repo is AGPL-3.0):

- Connector lines earn their place: any beam/rail/scan/underline must
  name both anchors and its job (reveal/route/validate) or be cut.
  Lands in motion-principles (composition) + svg-path-draw (constraints).
- Visuals point back to the source: when a video derives from concrete
  material, each frame's key visual should trace to a specific source
  line — real filenames/numbers over stock props. Lands as story-spine
  rule 4; the four SKILL.md index lines that enumerate story-spine's
  rules are synced.

Both are self-checks, not hard gates. lint:skills + skill-mirror green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skills): regen stale manifest + add emphasize to connector job list

Review 4975048154 follow-ups:
- skills-manifest.json was hashed mid-commit before oxfmt renormalized
  the four SKILL.md tables (lefthook pre-commit runs format and
  skills-manifest in parallel — they raced). Regenerated at head;
  second regen is a no-op.
- The connector rule's job list read literally would cut lines this
  same doctrine prescribes (dividers, hairlines, underline_sweep):
  emphasis was a missing job, not a forbidden one. Added 'emphasize'
  to both motion-principles and svg-path-draw.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 14:21:31 +08:00

123 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: svg-path-draw
description: Animate SVG paths drawing progressively using stroke-dasharray and stroke-dashoffset.
metadata:
tags: svg, stroke, draw, path, reveal, icon, vector
---
# SVG Path Draw
Reveals an SVG shape by animating its stroke as if a pen were tracing it. Two stroke properties together: **`stroke-dasharray = <pathLength>`** makes the entire path one dash; **`stroke-dashoffset`** starts at the path length (dash shifted fully out of view → invisible) and tweens to `0` (fully drawn). The length comes from the DOM API `path.getTotalLength()` — measured, never guessed.
Works on anything with a stroke: `<path>`, `<circle>`, `<rect>`, `<line>`, `<polyline>`, `<polygon>`, `<ellipse>`.
## Recipe
```html
<!-- inside a standard scene clip -->
<svg class="logo-mark" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<path id="bar-left" d="M 60 40 L 60 160" />
<path id="bar-right" d="M 140 40 L 140 160" />
<path id="bar-mid" d="M 60 100 L 140 100" />
</svg>
```
```css
.logo-mark path {
fill: none; /* outline-only draw — a fill would appear immediately and ruin the reveal */
stroke: {accentColor};
stroke-width: 12;
stroke-linecap: round; /* softer endpoints */
stroke-linejoin: round;
}
```
```js
// Setup: measure each path and set its dash pattern. Real measured geometry, not a magic number.
document.querySelectorAll(".logo-mark path").forEach((p) => {
const len = p.getTotalLength();
p.style.strokeDasharray = `${len}`;
p.style.strokeDashoffset = `${len}`;
});
// Stagger draws so the eye reads continuous motion — each segment starts at
// ~70-80% of the previous segment's duration, before it finishes.
tl.to(
"#bar-left",
{ strokeDashoffset: 0, duration: SEGMENT_DRAW_DUR, ease: "power2.out" },
SEG_1_START,
);
tl.to(
"#bar-right",
{ strokeDashoffset: 0, duration: SEGMENT_DRAW_DUR, ease: "power2.out" },
SEG_2_START,
);
tl.to(
"#bar-mid",
{ strokeDashoffset: 0, duration: FINAL_SEGMENT_DUR, ease: "power2.out" },
SEG_3_START,
);
// Companion wordmark fades in only after the last stroke settles.
tl.to(
".brand-line",
{ opacity: 1, duration: BRAND_FADE_DUR, ease: "power1.out" },
BRAND_FADE_START,
);
```
## Variations
- **Ring starting at 12 o'clock** — `<circle>` / `<rect>` strokes start at 3 o'clock by default; rotate the element `-90deg` so a progress ring draws from the top:
```html
<circle
cx="100"
cy="100"
r="60"
id="ring"
style="transform-origin: 100px 100px; transform: rotate(-90deg)"
/>
```
- **Linear (constant-speed) draw** — `ease: "none"` for a steady-rate "real pen" trace.
- **Draw then fill** — for filled shapes, tween `fillOpacity: 0 → 1` AFTER the stroke completes (requires `fill-opacity: 0` initially and a real `fill` in CSS):
```js
tl.to(
"#path",
{ strokeDashoffset: 0, duration: SEGMENT_DRAW_DUR, ease: "power2.out" },
SEG_1_START,
);
tl.to(
"#path",
{ fillOpacity: 1, duration: FILL_FADE_DUR, ease: "power1.out" },
SEG_1_START + SEGMENT_DRAW_DUR,
);
```
## Values
| token | range | notes |
| ----------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------- |
| SEGMENT_DRAW_DUR | 0.30.8s | fast snap vs deliberate pen trace; >~1s feels sluggish for a logo reveal |
| FINAL_SEGMENT_DUR | 6080% of SEGMENT_DRAW_DUR | proportional to segment length — a short connector at full duration reads slower than its siblings |
| SEG_N_START | previous start + 7080% of its duration | reads as continuous motion, not N isolated animations |
| SEG_1_START | 00.4s | a small ~0.2s lead-in lets the viewer settle before motion |
| BRAND_FADE_START | ≥ last stroke end (+ ~0.2s beat) | earlier and the wordmark competes with the draw |
| BRAND_FADE_DUR | 0.30.8s | snap (urgent) vs glide (premium) |
Ease families are discrete choices: **stroke draws** use `power2.out` (a hand lifting at end of stroke) or `none` for constant speed — never `back.out` / `elastic.out` (pens don't bounce). **Fades** use `power1.out`.
## Critical Constraints
- **`fill: none`** for outline-only draws — otherwise the fill appears immediately.
- **Dasharray/dashoffset = the measured `getTotalLength()`**, set at setup; requires the SVG in the DOM (inline SVG is fine; a loaded `<image>` SVG is not).
- **Complex paths**: if `getTotalLength()` looks wrong, overestimate slightly (`len * 1.05`) — too large is invisible at animation start; too small clips the end.
- **Stagger multi-path draws at ~7080%** of the previous segment's duration.
- **A drawn line must land on something.** When the path is a connector (rail, beam, underline, callout) rather than a shape, both endpoints must sit on real elements and the draw must do a job — reveal, route, validate, or emphasize. A stroke that only decorates empty space reads as filler; attach it or cut it.
## See also
`svg-icon-enrichment` (internal parts animate after the outline draws) · `counting-dynamic-scale` (stroke draws an icon while a number counts up) · `hacker-flip-3d` (logo draws, wordmark decodes beneath).