2.IDEA

JP only

SVGのアニメーションで線を引く方法まとめ(IEへの対応も)

SVGの利点は、前回の記事(SVGをIE等のブラウザ対応を考慮して使う方法まとめ(SVGのフォールバック画像など))でも触れたような「高解像度ディスプレイや拡大時でも劣化しない」がありますが、もうひとつの利点として「画像の柔軟なアニメーション」が挙げられます。

SVGはコードを元にブラウザが描画しているため、画像でも以下のようなアニメーションが可能になります(インラインSVGに限った話ではありますが)。

  • 線を引くようなアニメーション
  • パーツごとに独立したアニメーション
  • 色が変化するアニメーション
  • モーフィング

挙げるとキリがないのでこの辺でやめておきます。
今回はその中でも特徴的かつ初心者でも割ととっつきやすい、線を引くアニメーションの方法を紹介したいと思います。

目次

  1. 線を引くアニメーションの原理
  2. 実装方法
    1. pathの長さを取得して、stroke-dasharrayとstroke-dashoffsetを設定する
    2. stroke-dashoffsetを連続的に0に変化させることでアニメーションする
      1. CSSプロパティ(transitionやanimation)を使う
      2. setTimeout()を使う
      3. JavaScriptライブラリを使う
    3. インラインSVGが表示出来ないブラウザへの対応
  3. 自分が実装で使うなら&便利プラグイン紹介
  4. 参考

1. 線を引くアニメーションの原理

SVGの破線のプロパティである、stroke-dasharraystroke-dashoffsetを使うことで、線を引くアニメーションを実現出来ます。

プロパティの仕様については以下の画像を見ると理解しやすいかと思います。長さが200pxの<line>を想定しています。

stroke-dasharrayとstroke-dashoffsetの説明をする画像

「線の長さ(dash)」と「線同士の間隔(gap)」については、stroke-dasharray: (dash) (gap);のように指定します。
そして「開始位置(offset)」をstroke-dashoffset: (offset);のように指定します。

このすべてに「線の全長(画像でいう200px)」を指定すると、線が綺麗に隠れます。

stroke-dashoffsetを使って線が隠れることを説明する画像

この状態で、stroke-dashoffsetを0に変化させると、線を引くアニメーションが実現出来ます。

stroke-dashoffsetを使った線を引くアニメーションの仕組みを説明するgifアニメーション

2. 実装方法

それでは、SVGで線を引くアニメーションの実装方法について説明します。

2.1. pathの長さを取得して、stroke-dasharrayとstroke-dashoffsetを設定する

SVGの要素に使用出来るメソッドgetTotalLength()を使って、それぞれの<path>の長さを取得します。

取得した値を、stroke-dashoffsetstroke-dasharrayに設定することで、<path>たちを隠します。

var svg1 = document.getElementById("svg1"),
    paths = new Array();
// [].slice.call()によって、querySelectorAllで取ってきたNodeListでもforEachが使えるようになります。
[].slice.call( svg1.querySelectorAll('path') ).forEach( function( path, i ) {
    paths[i] = path;
    // 長さ取得。firefoxでは少し短くなるので適当な数字を足します。
    var leng = paths[i].getTotalLength() + 30;
    paths[i].style.strokeDasharray = leng + ' ' + leng;
    paths[i].style.strokeDashoffset = leng;
  } );

firefoxではgetTotalLength()で取得する値が、実際の長さより少し短くなります。なので、適当な数字を足すと良いと思います。

すべて<path>で描かれたSVGの場合です。他の要素(<rect><circle>など)も使用したい場合は、それらも<path>と同じ処理をしてください。

これでアニメーションをする準備ができました。

2.2. stroke-dashoffsetを連続的に0に変化させることでアニメーションする

1. 線を引くアニメーションの原理でも説明したように、stroke-dashoffsetを0に変化させることで線を引くアニメーションが実現できます。

変化させる方法は主に以下の3つがあります。

  1. CSSプロパティ(transitionやanimation)を使う→簡単だけどIE未対応
  2. setTimeout()を使う→IEも動くけど面倒
  3. JavaScriptライブラリを使う→簡単&IEでも動くけどサイズが膨大

それぞれ、長所・短所があるので、状況に応じて使い分けると良いかと思います(ちなみにjQueryはSVGの要素を扱えないので使えません)。

2.2.1. CSSプロパティ(transitionやanimation)を使う

前回の記事(UIアニメーション実装の最適解、CSS Transitions+class操作を使おう)で紹介したような、transitionを指定したプロパティの値をclass追加によって変化させることで、アニメーションさせる方法です。

先ほど、<path>要素に直接stroke-dashoffsetのスタイルを追加したので、追加したclassにはstroke-dashoffset: 0 !important;と指定すれば良いかと思います(要素に直接指定したスタイルをCSSで上書きするには、!importantが必要です)。

.svg1 path{
  -webkit-transition: stroke-dashoffset 1s ease;
  transition: stroke-dashoffset 1s ease;
}
.svg1.svg1--animated path{
  stroke-dashoffset: 0 !important;
}

さきほど言ったように、IEではアニメーションしません(IE11ですら)。何もない状態からいきなり表示されます。なので、代わりの表示方法を考えます。

ユーザーエージェントでIEかどうか判定して<svg>をwrapしている<div>をフェードインさせると良いかなと思います。

  • transitionが使えるIE10以上は、opacityを「transition+class追加」でフェードインする
  • transitionが使えないIE9以下は、最初から表示させておく
  • (jQueryを導入出来るなら、fadeIn()を使ってすべてのバージョンでフェードインできる)

IEでは線を引くアニメーションが見れませんが、「線を引くアニメーション自体、体験をリッチにするだけでコンテンツとは無関係」「簡単に実装したい」「軽量が良い」場合はこちらの方法が良いかと思います。windowsユーザーも最近はchromeを使っているようですし。

2.2.2. setTimeout()を使う

こちらは、setiTimeout()を使う、プリミティブなやり方です。以下のようなJavaScriptを使ってアニメーションを実現します(これだけコピペしても動きません。ちゃんとした実装方法はデモのソースをご覧ください)。

function draw(){
      progress = current_frame/total_frame;
    if (progress > 1) {
      // 現在のフレームと総フレームが等しくなれば終わり
      window.clearTimeout(handle);
    } else {
      // 1フレーム進める
      current_frame++;
      for(var j=0, len = paths.length; j<len; j++){
        // それぞれのpath要素のオフセットを縮める
        paths[j].style.strokeDashoffset = Math.floor(length[j] * (1 - progress));
      }
      handle = window.setTimeout(draw, 1000/60);
    }
  }

ここを参考にしまくっています。requestAnimationFrame()を使っていないのは、環境によっては表示が終わるまでにやたら時間がかかるためです(私が持っているAndroidでは異常な遅さでした...)。

2.2.3. JavaScriptライブラリを使う

以下のような、SVGを扱うJavaScriptライブラリにはアニメーション用のメソッドが用意されているので、簡単にアニメーションが実現出来ます。

例えばSnap.svgでは、以下のようなJavaScriptを使えばアニメーションできます。

/* pathを描画する部分 */
var snapsvg = Snap("#svg1");
var snappaths = new Array();

snappaths[0] = snapsvg.path("M237.7..........");
.
.
.
/* アニメーションの部分 */
function draw(){
  for (var i = snappaths.length - 1; i >= 0; i--) {
    snappaths[i].animate({strokeDashoffset: 0}, 1000);
  };
}

アニメーション自体はとても簡単です。パフォーマンスをそれほど気にしないのであれば、こちらを採用するのが良いかと思います。(なんだかんだpathを描画する部分で手間がかかった気がしますが...)

2.3. インラインSVGが表示出来ないブラウザへの対応

IE8以下やAndroid 3.0未満では、インラインSVGが表示出来ません。

対応としては、PNG等のフォールバック画像(代替画像)を用意するのが良いかと思います。SVGのフォールバックについては、以前書いた記事(SVGをIE等のブラウザ対応を考慮して使う方法まとめ(SVGのフォールバック画像など))を参考にしてみてください。

3. 自分が実装で使うなら&便利プラグイン紹介

場合にも依りますが、2.2.1. CSSプロパティ(transitionやanimation)を使うパターンで実装すると思います。

線を引くアニメーションは、スクロール位置を見て開始するケースが多い気がします。しかし、スクロール中にJavaScriptでゴリゴリ動かすとスクロールが詰まります(ました)
だからなんか嫌でした(transform: translateZ(0);でGPUアクセラレーションすると少しは変わるかもです)。

これらの他にも「オシャレに線を引くためのプラグイン(lazy line painter)」や「スクロールに合わせて線を引けるプラグイン(skrollr)」を使うやり方もあるので、目的に合えば使ってもいいかもしれません。

4. 参考