svg-stroke-reveal について
svg-stroke-revealは、ウェブプロジェクトにSVGの線が描かれる効果を加えるスキルです。ビューポート進入時に一度だけ再生するモードと、スクロール進捗に連動するモードを、標準のHTML、CSS、JSで実装できます。
主な機能
- 01SVGの線を段階的に表示
- 02ビューポート進入時に一度再生
- 03スクロール進捗に連動した描画
- 04モーション軽減設定への対応
- 05標準のHTML、CSS、JSを使用
活用シーン
- 01ロゴを画面内で描画
- 02署名やアイコンの描画を一度再生
- 03スクロールに合わせてルート図を表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rheinmir/ setup --skill svg-stroke-revealインストール後、Claude Code で/svg-stroke-revealを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/demo.htmlsvg-stroke-reveal.csssvg-stroke-reveal.js詳細
スキル:svg-stroke-reveal
Vanilla HTML/CSS/JS コンポーネント — SVG 内の <path> の線が、手で描いているように徐々に描画されます。ビューポートを通過するスクロール量(%)に応じて描画するか、ビューポートに入ったときに一度だけ描画します。外部ライブラリ(Anime.js、GSAP)は使用しません。
WHAT
目的とコンテキスト
- 目的: SVG 内の
<path>の線を、スクロール量(%)に応じて、またはビューポートに入ったときに一度だけ徐々に描画します(stroke-dasharray/dashoffset)。外部ライブラリを使わないバニラ実装で、デフォルトではRheinmir/uiux-assetのオリジナルをそのまま使用します。 - トリガー(使用するタイミング):
- ロゴ/署名/アイコンを、ビューポートに入ったときに「徐々に描画」するエフェクトが必要な場合(1 回だけ実行)。
- 折れ線グラフ、行程図、ルート線を、スクロールの進行に応じて継続的に「描画」する必要がある場合(実際のスクロール位置に連動し、1 回だけ実行するものではありません)。
- ユーザーが「スクロール時に SVG を描画」「svg stroke draw」「ロゴを徐々に描画」「path animation reveal」「stroke-dashoffset」または /svg-stroke-reveal と指定した場合。
- 対象外: 一般的な 7 つのスクロールエフェクト(opacity/translate reveal、stagger text、highlight、nav co、scroll-to-top、scrollspy、parallax)には使用しません。これらは
skills/scroll-effectsの対象です。アウトラインに変換していない<text>にも使用しません(Rules を参照)。
メンタルモデル
[data-svgr] > <path>×N → getTotalLength() → dasharray = dashoffset = length (非表示) → mode "once"(IO で一度だけ + CSS transition)または mode "scroll"(IO でフラグを有効化 + 1 つの listener rAF で progress に応じて dashoffset を更新)→ reduce-motion:dashoffset = 0 を即時設定
入力と出力の契約
| Field | Required? | 意味 | |
|---|---|---|---|
| In | プロジェクトの実際の SVG(線のある <path>) | はい | <text> は事前にアウトラインに変換する必要があります |
| In | mode once または scroll | はい | ロゴ/署名 → once;グラフ/行程 → scroll |
| In | プロジェクトの design.md | いいえ | 存在する場合は、それに合わせてカラー トークンを変更します |
| Out | [data-svgr] を付与した SVG + プロジェクト内の CSS/JS | はい | オリジナルファイルをそのままコピーするか、assets/ に合わせて調整します |
| Out | playwright-verify による検証の証拠 | はい | スクロール時の前/中/後の画像 + once は 1 回だけトリガーされること |
ルールと機能
- RULE-01 (MUST):
getTotalLength()は path の内部座標で測定され、CSS transform の影響を受けません。ただし、dasharray の設定後に、path を含む要素をtransform: scale(sx, sy)で拡大縮小し、sx != sy(2 軸で不均等に拡大縮小)にすると、描画速度が px 単位で 2 軸間で歪みます。アニメーションは元の length に沿って進む一方で、表示は縦横比が変わるためです。常にviewBox+width/heightでサイズを設定し、path を持つ要素自体への、軸ごとに異なる CSS scale は避けてください。 - RULE-02 (MUST):
<text>は stroke-draw できません。[data-svgr] を適用する前に、文字/フォントを実際のアウトライン<path>に変換する必要があります(Illustrator の「Create Outlines」、Figma の「Flatten」、または font-to-path 変換ツール)。<text>に直接適用しても、<text>にはgetTotalLength()がないため何も起こりません。 - RULE-03 (MUST):mode "once" は CSS transition を使います(滑らかで CPU 負荷が低い)。mode "scroll" は、スクロール位置と正確に同期させるため、JS でフレームごとに dashoffset を直接設定します。この 2 つの方法を逆にしないでください。
- RULE-04 (MUST):mode ごとに個別の
IntersectionObserverを 1 つずつ使います(once と scroll のロジックを 1 つの observer にまとめないでください。動作が異なるためです。once は 1 回で unobserve し、scroll はフラグを継続的に切り替えます)。また、ページ上の mode "scroll" のすべての SVG で共有する、rAF でスロットリングする scroll listener は 1 つだけにします。SVG ごとに個別のscrolllistener を複数設定しないでください。 - RULE-05 (MUST):
playwright-verifyで検証します。mode "scroll" の SVG をスクロールして通過する際、dashoffset が徐々に減少することが分かるように前/中/後の画像を撮影します。また、何度スクロールして出入りしても mode "once" の SVG が 1 回だけトリガーされることを assert します。 - 機能:オリジナルの取得(web/git)+ asset スキル +
design.mdの読み込み;HTML/CSS/JS の対象プロジェクトへの書き込み;ヘッドレスブラウザーを実行して検証。
失敗の境界
- アウトラインに変換していない
<text>に適用 → 何も起こりません:blocked。先に<path>への変換を依頼してください(RULE-02)。 - path を含む要素が CSS によって軸ごとに異なる倍率で拡大縮小される → 描画速度が歪みます:failed。サイズ設定を
viewBox+width/heightに変更してください(RULE-01)。 - 検証の assert が失敗(dashoffset が徐々に減少しない、once が再度トリガーされる)→ failed。修正してから再検証してください。
HOW
メインワークフロー
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | SVG + 要件 | mode を once と scroll のどちらかに選択します。SVG がすでに <path> か(<text> ではないか)確認します | mode | <text> → 変換するまでブロック |
| W02 | effect | 元の Pen テーブル | 実行リンクを開き、.html ファイルを丸ごと(+ _assets/)プロジェクトにコピーして、著作権表示コメントを残します | プロジェクト内の元ファイル | 外部ライブラリを追加できない場合 → B01 |
| W03 | effect | ファイル + 実際の SVG + design.md | 内容/画像/色のみを変更します(path の d= を置換) | 適用済み SVG | — |
| W04 | deterministic | HTTP 経由で実行するページ | playwright-verify で検証します(RULE-05) | 画像 + assert | 赤 → 修正して W04 を繰り返す |
分岐
| ID | Kind | Guard | 動作 | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | プロジェクトで外部ライブラリを追加できない(元のバージョン) | vanilla の簡易版 assets/ を使用します(Reference — Assets を参照)。読み込んでから ADAPT します | — | W04 |
| B02 | conditional_required | prefers-reduced-motion: reduce | 初期化時に strokeDashoffset = 0 にし、observer/listener を省略します | — | W04 |
検証と停止条件
W04 で決定論的な assert を実行します(scroll モード:画像の前/中/後で dashoffset が段階的に減少すること、once モード:スクロールで画面外に出て戻っても trigger は 1 回のみ)。目視で確認する項目:滑らかさ、線を描く順序。assert が緑になったら停止します。
例
- 成功例:「画面に入るとロゴが徐々に描画される」→ 4 つの path を持つロゴに
data-svgr="once"を設定 → viewport の 30% に入ると順番に描画されます(DOM 順に 160ms の stagger)。画面外にスクロールして戻っても再描画されません。 - 境界/失敗例: 署名が script フォントの
<text>→getTotalLength()がなく、何も描画されません → 停止し、[data-svgr]を適用する前に、アウトラインに変換(Figma の「Flatten」)して<path>にするよう依頼します。
Reference — 元のバージョン — 先に使用(ファイルを丸ごとコピーし、書き直さない)
作者のオリジナルコードは一字一句そのまま、外部リポジトリ Rheinmir/uiux-asset にあります(公開 CodePen = MIT)。実際に動作するギャラリー: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 | 実行 · コード |
このスキルの assets/ は、自作の vanilla 簡易版です。プロジェクトで外部ライブラリを追加できない場合に使用してください。通常は上記の元のバージョンを使用します。
Reference — Assets
assets/demo.html + assets/svg-stroke-reveal.css + assets/svg-stroke-reveal.js — 読み込んでから、プロジェクトの実際の SVG に合わせて ADAPT します(path の d= を置換し、design.md があれば色のトークンを変更します)。そのまま機械的にコピーしないでください。
リファレンス — 仕組み
[data-svgr]内の各<path>に対して、初期化時に JS がpath.getTotalLength()を呼び出し、インラインのstyle.strokeDasharray = lengthとstyle.strokeDashoffset = lengthを設定します → 線が完全に非表示になります(ダッシュの長さを path の全長と同じにし、offset でダッシュ全体を外側へ押し出します)。- Mode
data-svgr="once"(1回のみ発動するしきい値 — ロゴ/署名):しきい値 0.3 のIntersectionObserverを使います。ビューポートに入ると、すべての子 path のstrokeDashoffset = 0を設定します → CSS のtransition: stroke-dashoffsetが1回だけ実行されます → 発動後すぐにunobserveするため、スクロールして一度外れて再び入っても繰り返しません。 - Mode
data-svgr="scroll"(連続的な進捗 — グラフ/道のり):IntersectionObserverは「ビューポート内にある」フラグのオン/オフだけを切り替えます。フラグがオンの間、rAF でスロットルしたスクロールリスナー(リスナーは1つだけ共有し、複数作成しません)がprogress = (innerHeight - rect.top) / (innerHeight + rect.height)を 0..1 に制限して計算し、各フレームでstrokeDashoffset = length * (1 - progress)を直接設定します。このモードでは CSS transition を使用しません(transition を使うと、線の描画が実際のスクロール位置に追従できなくなります)。 - 子の
<path>が多数ある複雑な path(線の多いロゴ、署名)では、mode "once" の stagger にtransition-delay: calc(var(--svgr-i) * 160ms)を使い、JS が DOM 内の path の順番(最初の path が先に描画されます)に従って--svgr-iを割り当てます。 prefers-reduced-motion: reduceの場合 → JS は初期化時にすべての path のstrokeDashoffset = 0を即時設定し、両方の observer/listener をスキップします。アニメーションなしで全体の線をすぐに表示します。