この記事でできること
Vivus.jsはSVGのパス(<path>)を線描きするようにアニメーションさせるライブラリです。本記事では、スクロールで画面内に入るたびに、手書き風のSVGアニメーションが再発火する実装を、HTMLとJavaScriptのコードとともに解説します。
具体的には次の技術を組み合わせます。
- Vivus.js — SVGのパスを手書き風にアニメーション
- IntersectionObserver API — スクロール検知(パフォーマンス効率が高い)
- SVGマスク — 下地の画像をパスでなぞって「あぶり出す」演出
参考記事:本記事は evoworx様の解説記事 を参考にしつつ、「スクロールのたびにアニメーションが再生される」機能を追加した実装を紹介しています。
完成サンプル
このように、スクロールする度に手書き風のアニメーションが発火します。
この方法について詳しく解説していきます。
(このサンプルは、ページ最後にも登場します。)
アニメーションの仕組み(マスクレイヤーとあぶり出し)
今回の実装では、SVGの マスク(<mask>) 機能を活用します。仕組みをシンプルに説明すると次のとおりです。
- 下地レイヤー(
<image>タグなど)を配置する - その上にマスクのパスを重ねる
- Vivus.jsがマスクのパスを描画するに連れて、下地の画像がなぞった部分だけ表示される
この方式のメリットは、下地のデータに品質上の問題があっても動かせる点です。マスク側のパスさえきれいに書けていれば、下地は写真でも画像でも自由に差し替えられます。

スクリーンショットのように、マスクとベースレイヤーの構造図マスクレイヤー(白いパス)がアニメーションすることで、下地のベース画像がなぞるように表示される仕組みです。
Vivus.jsの導入方法
① npm / yarn でインストール(推奨)
モジュールバンドラー(Vite、webpackなど)を使っているプロジェクトでは、npmでインストールするのが最も管理しやすい方法です。
Terminal
# npm
npm install vivus
# yarn
yarn add vivusインストール後、JavaScriptファイルでインポートします。
JavaScript (ESモジュール)
import Vivus from "vivus";② CDNで読み込む(手軽に試したい場合)
ビルドツールを使わないシンプルなHTMLプロジェクトや、動作確認だけしたい場合はCDNが便利です。
HTML
<script src="https://cdn.jsdelivr.net/npm/vivus@latest/dist/vivus.min.js"></script>③ ファイルをダウンロードして使う
GitHubのページ から vivus.min.js をダウンロードし、プロジェクトに配置してscriptタグで読み込みます。バージョンを固定したい場合や、オフライン環境に向いています。
HTML
<script src="./assets/js/vivus.min.js"></script>SVGの用意と構造の作り方
Vivus.jsが動かせる要素は <path> のみ です。IllustratorやFigmaで作成したSVGにpolylineやrectが含まれている場合は、事前にpathへ変換する必要があります。
⚠️ 重要SVGの内部は <path> 要素のみにしてください。<polyline>、<rect>、<circle> 等はVivusで動きません。
Illustratorではオブジェクトをすべて「複合パス」へ変換してから書き出しましょう。
マスク用SVGの作成手順の例:
- Illustratorで下地となる画像(ロゴ、テキスト画像など)を用意する
- その上に「なぞるルート」となる線のパスを手描きで重ねる
- SVGとしてパスのみをエクスポートする(マスク用)
- 下地の画像はPNG、SVG、JPGなどとして別途用意する
CSSの設定
SVGをレスポンシブに表示し、マスクの色(白)を設定するためのCSSです。
マスクは白色のストローク(stroke: #fff)でないと正しく機能しません。
CSS / SCSS
//画像全体の親要素
.svg-anim {
display: block;
svg {
width: 100%;
* {
width: 100%;//要素全体を親要素に合わせる
}
}
}
//SVGのパスに設定するクラス
.path-1 {
fill: none;
stroke: #fff; //マスクの色
stroke-width: 9;
stroke-miterlimit: 10;
}stroke-width はマスクが下地をどの程度の幅で「なぞる」かに相当します。
値を大きくすると太い筆で塗るような演出になるので、下の要素を覆うくらい太くする必要があります。
下地画像の大きさに合わせて太さを調整してください。
HTMLの実装
SVGの viewBox に下地の画像の縦横比に合わせた値を指定することで、画像とぴったりフィットします
(例:viewBox="0 0 123 43")。
HTML
<span class="svg-anim">
<svg
id="svg-anim01"
viewBox="0 0 123 43" <!-- 下地画像の縦横比に合わせる -->
width="100%"
height="auto"
>
<defs>
<mask id="clipMask">
<g id="Mask">
<!-- マスクのpathを貼り付け -->
<path
class="path-1"
d="M3.53,15.5l16.67-3.92-6.91-6.71-1.7,26.94..."
/>
</g>
</mask>
</defs>
<g id="Base" mask="url(#clipMask)">
<!-- ベースレイヤー(下地の画像)のhrefを設定 -->
<image
href="./publish/img/sec1-1_img1-text.svg"
alt="お悩み"
width="123"
height="43"
loading="lazy"
/>
</g>
</svg>
</span><mask> 内のパスがVivus.jsによって描かれていくことで、mask="url(#clipMask)" を指定したグループ(Base)の下地が徐々に現れます。
JavaScriptの実装(スクロール発火)
元のVivus.jsのシンプルな使い方に、IntersectionObserver を組み合わせることで「スクロールして画面内に入るたびにアニメーションが再生する」動作を実現します。
JavaScript (ESモジュール)
// vivus.jsでSVGアニメーション=============
import Vivus from "vivus";
document.addEventListener("DOMContentLoaded", () => {
const svgEl = document.getElementById("svg-anim01");
if (!svgEl) return;
svgEl.style.visibility = "hidden"; // 初期状態は非表示
let vivusInstance = null;
let timer = null;
const svgObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 遅延後にアニメーション発火
timer = setTimeout(() => {
svgEl.style.visibility = "visible"; // アニメーション直前に表示
if (vivusInstance) {
vivusInstance.reset().play();
} else {
vivusInstance = new Vivus("svg-anim01", {
type: "oneByOne",
duration: 100,
animTimingFunction: Vivus.EASE,
start: "autostart",
});
}
}, 400); // 遅延時間(ms)
} else {
if (entry.intersectionRatio === 0) {
clearTimeout(timer); // 画面外に出たらタイマーをキャンセル
svgEl.style.visibility = "hidden"; // 初期状態に戻す
}
}
});
},
{
threshold: [0, 0.3],
rootMargin: "0px 0px -10% 0px",
},
);
svgObserver.observe(svgEl);
});コードの流れ
- ページ読み込み時はSVGを
visibility: hiddenで非表示にする - IntersectionObserverでSVG要素を監視する
- 画面内(30%以上)に入ったら400ms後にVivusを発火。初回はインスタンス生成、2回目以降は
.reset().play()で再生 - 完全に画面外に出たら
visibility: hiddenに戻してリセット待機
完成サンプル
Vivusのオプション解説
| オプション | 値の例 | 説明 |
|---|---|---|
type | "oneByOne" | パスを1本ずつ順番に描画。他に delayed、sync など |
duration | 100 | アニメーション全体のフレーム数。大きいほどゆっくり |
animTimingFunction | Vivus.EASE | イージング関数。LINEAR、EASE_OUT なども選択可 |
start | "autostart" | インスタンス生成直後に再生開始 |
実装のポイントとよくある落とし穴
① pathに変換し忘れる
IllustratorやFigmaから書き出したSVGには <rect>、<circle>、<polyline> が含まれることがあります。これらはVivusでアニメーションされません。書き出し前に「オブジェクトをパスに変換」する作業が必須です。
② SVG IDとVivusの第一引数を一致させる
new Vivus("svg-anim01", ...) の第一引数は、SVGタグの id 属性と完全に一致させてください。複数のSVGアニメーションを使う場合はIDが重複しないよう注意します。
③ threshold と rootMargin の調整
threshold: [0, 0.3] は「要素が0%表示されたとき」と「30%表示されたとき」の2つのタイミングで判定します。スマートフォンなど画面が小さい端末ではSVGが一瞬で画面を通過することがあるため、rootMargin を調整してアニメーションが確実に見えるようにしましょう。
④ visibility vs display の使い分け
display: none ではなく visibility: hidden を使うのは、要素のサイズを保持したままにするためです。display: none にするとレイアウトが崩れることがあります。
⑤ 複数のSVGに同じ処理を適用する場合
複数のSVGに適用する場合は、各SVGに対してObserverのインスタンスを生成するか、querySelectorAll でループ処理して個別にVivusインスタンスを管理します。
複数SVGへの応用document.querySelectorAll("[data-svg-anim]") のようにdata属性で対象を選択し、forEach でループする設計にすると複数のSVGを一括管理しやすくなります。
まとめ
Vivus.jsとIntersectionObserverを組み合わせることで、「スクロールするたびにSVGのパスアニメーションが再生される」インタラクティブな演出を実現できます。
また、SVGマスクを使った「あぶり出し」の手法により、下地の画像をそのまま活用しながら魅力的なアニメーション表現が可能です。
実装のチェックポイントをまとめると以下のとおりです。
- SVG内の要素はすべて
<path>に変換する - マスクのストロークカラーは白(
#fff)にする viewBoxの縦横比を下地画像に合わせる- 初回は
new Vivus()、2回目以降は.reset().play()で再生 visibility: hidden / visibleで表示を制御する
ぜひ自分のプロジェクトにカスタマイズして取り入れてみてください。

コメント
コメント一覧 (1件)
[…] ます。→ 詳細はこちら:Vivus.jsでSVGをスクロールのたびに手書き風アニメーションを再生させる実装方法 […]