SVGで文字をなぞるアニメーションの実装

> 2018/06/27
> SVGに詳しくなりたい

社内のSlackでそう呟いた時から、かれこれ1年半ぐらいの時間が経ちました。

「Scalable Vector Graphics」、通称SVG。

あの頃と比べると今では少し仲良くなれてきた気がしますが、仲良くなるたびにその身に潜む深淵がどんどん明るみになりより深さが増していく、ステキな技術だと感じている今日この頃、Qiita初投稿の題材として選ばせていただきました。

初投稿は2MBまでしか画像を使えない仕様をつい数日前に知ったので、この記事はCodePenによる提供多めでお届けします。。

SVGがHTMLに埋め込める形式で助かった。

# あらすじ

とある案件で、メインビジュアルに置いたキャッチコピーにアニメーションを付けたい！という相談があったため、SVGで書かれた文字を動かす方法について調べました。

サンプルとして適当なダミー画像を用意しましたが、

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="html,result" data-user="m_shinada" data-slug-hash="eYmzXeK" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Snowrobin_stroke_one">
  <span>See the Pen <a href="https://codepen.io/m_shinada/pen/eYmzXeK">
  Snowrobin_stroke_one</a> by m_shinada (<a href="https://codepen.io/m_shinada">@m_shinada</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

こんな感じの一筆書きで繋がっている文字や、


<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="html,result" data-user="m_shinada" data-slug-hash="GRgqeQo" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Snowrobin_stroke_fill">
  <span>See the Pen <a href="https://codepen.io/m_shinada/pen/GRgqeQo">
  Snowrobin_stroke_fill</a> by m_shinada (<a href="https://codepen.io/m_shinada">@m_shinada</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

こんな感じの筆記体で書かれた文字だとそれっぽく見え（る気がし）ます。

これらのSVG画像に対して、手書き風になぞるようなアニメーションを付けていきます。

# 前提条件
- SVG画像はHTMLに **インライン** で埋め込むこと
  - inline SVG というやつです
  - SVGのパラメータを制御するためには `<img>` タグなどではアクセスできないため
- **必ずアウトライン化されている（文字情報が失われている）**こと
  - illustratorなどでフォントを元に画像を作る場合に忘れがち
  - パスを用いたアニメーションなので文字情報のままだと動かせません

# 仕組み

カギとなるのはSVGにおける `stroke-dasharray` と `stroke-dashoffset` という2つのプロパティ。
この2つは互いに `stroke-dash...` つまり **破線** に関するプロパティですね。

`----------------------------------------------------`
↑こういうふうな、等間隔に線とアキが繰り返される線を破線といいます。

それぞれ、

- `stroke-dasharray` は 破線の間隔（長さ）
- `stroke-dashoffset` は 破線の開始位置

を制御するプロパティとなっています。

「いやでもさっきの画像に破線なんてないですやん」という話なのですが、 `stroke-dasharray` を **ある値** に設定することで先ほどの画像中の線を破線と同じ扱いにすることができます。
この **線と見た目が同じ破線** に変換することが、文字をなぞるアニメーションを実装するための第一歩となります。

## stroke-dasharray

**線と見た目が同じ破線** は、`stroke-dasharray` を **線の長さと同じ値** にすることで実現できます。
破線のひとかけらの長さが線の長さと同じになることで、見た目は同じなのに破線である……つまり**破線のひとかけらがバカでかいためにアキの部分が範囲外に押し出された**状態になります。
これにより、線の見た目は変わらないまま破線扱いになるため、破線の開始位置を制御する `stroke-dashoffset`が意味をなすようになります。

## stroke-dashoffset

`stroke-dashoffset`の値を変えることで、破線の開始位置を線の流れに沿って動かすことが可能です。
しかし先ほどの設定によって元々の線と同じ長さとなった破線のひとかけらに対して、 `stroke-dashoffset`の値を大きくしていくと、まるで線がスルスルっと外側へ巻き戻るように消えていく**ように見えます**。
しまいにこちらも **線の長さと同じ値** まで辿り着くと、線は完全に見えない状態になってしまいます。
これは、線の長さめいっぱいの破線のひとかけら分と同じだけ位置がズレたことにより、**破線のアキの部分のみが表示された（つまり何も見えない）状態**になっているのです。

- 最小……線が表示された状態
- 増加……線が巻き戻るように消えていく
- 最大……線が消えた状態

簡単に遷移をまとめるとこのようになりますが、`stroke-dashoffset` を徐々に増加させることで、線が巻き戻って消えるように見えるならば、これと逆の動き、つまり`stroke-dashoffset`を **最大（線の長さと同じ）から最小（0）へ徐々に戻す** ことで、まるで一本の線をなぞり書いたような動きを実現することができるのです！

この辺りの挙動の理解については以下のページにあるデモがとても参考になります:bow:
https://jakearchibald.com/2013/animated-line-drawing-svg/

## パスの長さ

ところで、「線の長さ」とはSVGにおける**パスの長さ**になります。
一筆書きの線のみで構成された画像の場合は、パスの始点から終点までの長さすべてがパスの長さになります。
こうした場合は、illustratorでSVGファイルを開き、「ウィンドウ」→「ドキュメント情報」から、右上のメニューで「オブジェクト」を選択することですぐにパスの長さを知ることができます。

![ai_info_pathlength.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528753/0e41f964-6d40-c49f-a7e7-abf38b360a77.png)

1文字ずつバラけた画像の場合は、各文字の一画ごとにパスの長さが異なるので、一つ一つ調べるよりかは、JSで取得する方が楽かもしれません。


# 実装

## 一筆書きの場合

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="html,result" data-user="m_shinada" data-slug-hash="QWwEzxY" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Snowrobin_strokeAnimation_css">
  <span>See the Pen <a href="https://codepen.io/m_shinada/pen/QWwEzxY">
  Snowrobin_strokeAnimation_css</a> by m_shinada (<a href="https://codepen.io/m_shinada">@m_shinada</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

CSSの `@keyframe` と `animation` を用いて `stroke-dashoffset` の値を変動させることで、なぞるような動きを実現できています。
実装もわかりやすくて楽なのですが、欠点として **IE11** では `stroke-dashoffset` をCSSで段階的に変動させることができないため動作しない、そもそも一筆書きでないと使えないという欠点があります。。

## 1文字ずつの場合

<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="html,result" data-user="m_shinada" data-slug-hash="JjoKzbw" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Snowrobin_strokeAnimation_vivus">
  <span>See the Pen <a href="https://codepen.io/m_shinada/pen/JjoKzbw">
  Snowrobin_strokeAnimation_vivus</a> by m_shinada (<a href="https://codepen.io/m_shinada">@m_shinada</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

文字がバラけている場合、一画ずつにパスの長さが変わるため、パラメータの取得と制御を [vivus.js](https://maxwellito.github.io/vivus/) にお願いしたパターンです。
こちらはJSにより段階的な制御をしているため、`IE11` でも機能するはず……？
埋め込みの方がうまく動いていない場合は [codepen](https://codepen.io/m_shinada/pen/JjoKzbw) の方だときちんと動いているかと思います。

あらすじで言っていた案件ではこちらの方法を採用しましたが、実はこの実装では文字本体ではなく、文字にかけている**マスクデータのパス**を制御しています。
文字を覆い隠す太さの線でマスクデータを文字に合わせて一画ずつ作り、徐々にマスクを剥がすようにして文字を表示していくことで、手書きのようなアニメーションに見えているかと思います。

マスクデータの作成や挙動については、こちらのページを参考にさせていただきました:bow:
https://www.willstyle.co.jp/blog/1569/

# まとめ
この動かし方を最初に思いついた人はすごい。調べれば調べるほどいろんなことができて面白いですねSVG。

今回は（急ぎめの実装だったのもあって）ライブラリに頼るところも大きかったので、自前のJSで細かく制御などもしてみたい。。