Inside cmn!

山本 慎二

SVGで手書き文字アニメーションを作る方法2026年版

UI/UXプログラミングTips

SVGアニメーションのイメージ

手書き文字がすーっと描かれていくようなアニメーション。
実はSVGとちょっとした実装で簡単に作ることができます。

このテーマについては2016年にも一度取り上げたことがあるのですが、当時はアニメーション部分をjQueryで実装していました。あれから約10年、今ならもっとシンプルかつ柔軟に実装できるので、このタイミングで仕組みからまるごと書き直してみたいと思います。

全体の流れ

手書き文字の描画アニメーションは、大きく分けて以下の4ステップで作ります。

  1. 手書き文字のアウトラインを作成する
  2. 手書き文字のストロークを作成する
  3. SVGコードを出力して、ストロークにclipPathを適用する
  4. 線が描かれていくアニメーションを実装する

順番に見ていきます。

1. 手書き文字のアウトラインを作成する

まずIllustratorで、手書き文字の輪郭を作成します。
今回は例として「Hello World!」を、Google FontsのPacifico Regularで作ってみます。

文字のデザインが決まったら、全体をアウトライン化し、パスファインダーで合体させ、最後に複合パス化してください。レイヤー名は、SVG書き出し時にそのままidになるので、わかりやすい名前をつけておくと後の作業が楽になります。可能であれば「パスの単純化」でアンカーポイントを減らしておくと、書き出されるSVGコードも少し軽くなります。

2. 手書き文字のストロークを作成する

輪郭ができたら、別レイヤーにパスツールで文字の「ストローク(線)」部分を作成します。ポイントは、輪郭のもっとも太い部分に合わせた太さのパスにして、輪郭の内側がきれいに塗りつぶされるようにすることです。

文字が複数行・複数パーツに分かれている場合、ストロークを1本のパスにつなぐか、パーツごとに分けるかは、後段のアニメーションの実装方法によって決めればOKです。

  • 単純にCSSやstroke-dashoffsetの手動操作で一筆書きのように滑らかに描かせたい場合は、1本のパスにつないでおくのが簡単です。複数本に分かれていると、各パスの先頭から同時に描画が始まってしまうためです。
  • GSAPを使う場合は、staggerオプションで複数パスに少しずつ時間差をつけて個別にアニメーションさせられるので、パーツごとに分けたまま作っても扱いやすくなります(後述)。

3. SVGコードを出力して、ストロークにclipPathを適用する

llustratorの「別名で保存」→SVG形式を選び、保存画面の「SVGコード…」から書き出されたコードをコピーします。

このコードを使って、文字の輪郭で線を切り抜くためのclipPathを定義します。

  • <svg ...>の直後に<defs></defs>を追加
  • <defs>の中に、輪郭部分のグループ(<g id="...">...</g>)を貼り付け、<g><clipPath>に書き換える(または全体を<clipPath>で囲む)
  • ストローク側のタグにclip-path="url(#クリップパスのid)"を指定する(clipPathではなくclip-pathなので注意)
<svg viewBox="0 0 600 200">
  <defs>
    <clipPath id="textClip">
      <!-- Illustratorから書き出した文字の輪郭パス -->
      <path d="..." />
    </clipPath>
  </defs>
  <path id="stroke" d="..." clip-path="url(#textClip)"
        fill="none" stroke="#222" stroke-width="40" />
</svg>

ここまでできれば、ブラウザ上でストロークが文字の輪郭通りに切り抜かれているのが確認できるはずです。

4. 線が描かれていくアニメーションを実装する

ここからが本題です。線が手書きで描かれていくように見せる仕組みは、SVGのstroke-dasharraystroke-dashoffsetという2つのプロパティを使います。

  • stroke-dasharray:パスを破線にするプロパティ。パスの全長以上の値を指定すると、実質「実線→透明」の2区間だけの破線になる
  • stroke-dashoffset:破線の開始位置をずらすプロパティ。パスの全長分ずらすと、線が完全に見えない状態になる

つまり、stroke-dashoffsetをパスの全長 → 0まで変化させると、線がじわじわと描かれていくように見える、という仕組みです。

パスの全長は、固定値を手入力する必要はなく、JSのgetTotalLength()で動的に取得できます。

const stroke = document.querySelector('#stroke');
const length = stroke.getTotalLength();

stroke.style.strokeDasharray = length;
stroke.style.strokeDashoffset = length;

ここからstroke-dashoffsetを0まで変化させる方法として、今回は2パターンご紹介します。

方法①:Web Animations API(ライブラリ不要)

ブラウザ標準のElement.animate()(Web Animation API)を使えば、追加ライブラリなしでstroke-dashoffsetをアニメーションできます。

const stroke = document.querySelector('#stroke');
const length = stroke.getTotalLength();

stroke.style.strokeDasharray = length;
stroke.style.strokeDashoffset = length;

stroke.animate(
  [
    { strokeDashoffset: length },
    { strokeDashoffset: 0 }
  ],
  {
    duration: 4000,
    easing: 'ease-in-out', // 'cubic-bezier(...)' で細かい指定も可能
    fill: 'forwards'
  }
);
  • ライブラリ不要で軽量
  • 戻り値のAnimationオブジェクトで.pause() / .play() / .reverse() / .finished(Promise)といった制御ができる
  • イージングはCSSのイージング関数(ease / cubic-bezier() / steps()など)の範囲に限られる
  • 複数パスを順番に描く・スクロールに連動させるといった複雑な制御は自分で組む必要がある

ロゴのワンポイント演出のように、1〜数本のパスをワンショットで描くだけならこれで十分なケースが多いです。

以下のデモでは、手書きのストロークをいくつかに分割した上で、順番に描画する処理を行っています。
それぞれのストロークのアニメーションは完了後に commitStyles()style属性として書き込んで固定した後にcancel()で削除しています。アニメーションは静的なスタイルよりも優先されるため、こうしておくことで状態をリセットして再度アニメーションをさせたい場合などに不具合が起こりにくいです。

方法②:GSAP + DrawSVGPlugin

複数の文字・複数行を順番に描いたり、スクロールに連動させたり、緩急の効いたイージングを使いたい場合は、GSAPのDrawSVGPluginが便利です。

GSAPにはDrawSVGPluginをはじめとした拡張プラグイン(MorphSVGPluginSplitTextなど)が用意されており、こうした複雑な描画アニメーションも簡潔に書けるようになっています。

import { gsap } from 'gsap';
import { DrawSVGPlugin } from 'gsap/DrawSVGPlugin';

gsap.registerPlugin(DrawSVGPlugin);

gsap.set('#stroke', { drawSVG: '0%' });
gsap.to('#stroke', {
  drawSVG: '100%',
  duration: 4,
  ease: 'power2.inOut'
});

drawSVGは内部でstroke-dasharray / stroke-dashoffsetを操作してくれるので、getTotalLength()stroke-dasharrayの手動設定すら不要です。緩急の効いたイージングもease: 'power2.inOut'の一行で指定できます。

複数行・複数パスを順番に描きたい場合は、タイムラインやstaggerを使うと簡潔に書けます。

gsap.timeline()
  .from('.stroke-line1', { drawSVG: '0%', duration: 2, ease: 'power1.inOut' })
  .from('.stroke-line2', { drawSVG: '0%', duration: 2, ease: 'power1.inOut' }, '-=0.5'); // 少し重ねて開始

// あるいは複数パスをまとめてstagger再生
gsap.from('.stroke', {
  drawSVG: '0%',
  duration: 2,
  ease: 'power1.inOut',
  stagger: 0.3
});

最初にWeb Animation APIで実装したデモをGSAPを使って書き換えたものがこちらになります。

さらに、スクロールに合わせて文字を描かせたい場合は、ScrollTriggerと組み合わせるだけです。

import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(DrawSVGPlugin, ScrollTrigger);
gsap.from('#stroke', {
  drawSVG: '0%',
  ease: 'none',
  scrollTrigger: {
    trigger: '#stroke',
    start: 'top 80%',
    end: 'top 30%',
    scrub: true
  }
});

どちらを選ぶか

Web Animations API GSAP + DrawSVGPlugin
導入コスト 不要(ブラウザ標準) ライブラリ読み込みが必要
記述量 やや多め(getTotalLength等を自分で書く) 少ない(drawSVGが長さ計算を内包)
イージング CSSイージング関数のみ GSAP独自の豊富なイージング・カスタムイージング
複数パスの連携・スクロール連動 自前実装 タイムライン・staggerScrollTriggerで簡潔に書ける
向いているケース ワンショットで1〜数本描くだけ 複数文字・複雑な順序・スクロール演出・凝った緩急

ロゴのワンポイント演出くらいならWeb Animations APIだけで十分ですが、サイト全体の複数箇所で使う、スクロールに連動させる、といった要件があるならGSAPを利用した方がコードとしてはシンプルに書けるという印象です。

まとめ

  • 手書き文字アニメーションの基本は アウトライン作成→ストローク作成
  • アニメーション自体は、シンプルな用途ならWeb Animations API、複雑な制御をしたいならGSAPdrawSVG)がおすすめ
  • GSAPのDrawSVGPluginScrollTriggerなどのプラグインを使えば、複数文字の順番制御やスクロール連動も簡潔に書ける

仕組みがわかってしまえばシンプルな実装ですが、組み合わせることによって面白い演出もできると思います。ロゴのアニメーションなど使えるシーンも多いので、ぜひ試してみてくださいね。

Written by

山本 慎二 Shinji Yamamoto

front-end engineer

1987年に東京で生まれたが、1歳からは北海道で育つ。
2010年に札幌市立大学デザイン学部製品デザインコースを卒業後、イベント運営を行う会社に勤務し業務の一環でHTML/CSSに触れる。
2016年にcommono立ち上げ時から参加し、フロントエンドエンジニアとして学びながら現在に至る。

Q1. commonoに参加した理由
矢野さんが立ち上げメンバーを探しているときに、知人の紹介でお会いし、ここでなら前向きにワクワクできるようなことができると感じて参加させていただきました。
Q2. わたしの偏愛
マイホーム(今年こそピザ窯を)
Q3. 今後commonoというフィールドでやりたいこと
クライアント様やデザイナーと一緒にユーザーの心に残るような体験をWEBで表現する手伝いをしていきたいです。

Recommend

お問い合わせ

    個人情報利用目的への同意

    ※このサイトはCloudflare Turnstileよって保護されています。
    ※お客様が入力した個人情報はSSL暗号化信号によって保護されています。

    COMMONO株式会社
    E-mail:info@commono.co.jp
    個人情報保護方針、個人情報の取扱いについて
    PRIVACY POLICY

    お気に入り

    お気に入りから削除

    選択したアイテムをお気に入りから削除しますか?