ザリガニデザインオフィス

illustrator + anime.jsで動く円グラフのサムネイル

illustrator + anime.jsで動く円グラフ

Adobe illustratorで作ったベクトルの円グラフをanime.jsで動かす方法です。ここで主に紹介するのは、真ん中に穴の空いたドーナツグラフです。完全な円グラフもできないことはないのですが、いくつか条件があります(後述)。

anime.jsの詳しい使い方は説明しませんので、anime.js自体の解説などは他のサイトなどをご参照ください。

以下が全体コードになります。

HTML

<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In  -->
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
  x="0px"
  y="0px"
  width="493.3px"
  height="493.3px"
  viewBox="0 0 493.3 493.3"
  style="enable-background:new 0 0 493.3 493.3;"
  xml:space="preserve"
  id="svg1"
>
  <style type="text/css">
    .st0 {
      fill: none;
      stroke: #bdccd4;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st1 {
      fill: none;
      stroke: #ff7bac;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st2 {
      fill: none;
      stroke: #ff1d25;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st3 {
      fill: none;
      stroke: #ff931e;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st4 {
      fill: none;
      stroke: #7ac943;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st5 {
      fill: none;
      stroke: #3fa9f5;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
  </style>
  <defs></defs>
  <path class="st0" d="M213,34.6c11-1.7,22.2-2.6,33.7-2.6" />
  <path class="st1" d="M173.3,44.8c12.7-4.6,26-8.1,39.7-10.2" />
  <path class="st2" d="M102.7,87.4c20.4-18.4,44.3-33,70.7-42.6" />
  <path class="st3" d="M34.4,214.7c7.5-50.2,32.4-94.8,68.3-127.3" />
  <path
    class="st4"
    d="M175.8,449.3C92,420.1,32,340.4,32,246.7c0-10.9,0.8-21.5,2.4-31.9"
  />
  <path
    class="st5"
    d="M246.7,32c118.6,0,214.7,96.1,214.7,214.7s-96.1,214.7-214.7,214.7c-24.8,0-48.7-4.2-70.9-12"
  />
</svg>

JavaScript

//アニメーション
var chart = anime.timeline();
chart
  .add({
    targets: ".st5",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 550,
    delay: 0
  })
  .add({
    targets: ".st4",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 250,
    delay: 0
  })
  .add({
    targets: ".st3",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 100,
    delay: 0
  })
  .add({
    targets: ".st2",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 80,
    delay: 0
  })
  .add({
    targets: ".st1",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 40,
    delay: 0
  })
  .add({
    targets: ".st0",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 40,
    delay: 0
  });
//Pathの長さを取得し、オフセットする
var svg1 = document.getElementById("svg1"),
  paths = new Array();
[].slice.call(svg1.querySelectorAll("path")).forEach(function(path, i) {
  paths[i] = path;
  var leng = paths[i].getTotalLength();
  paths[i].style.strokeDasharray = leng + " " + leng;
  paths[i].style.strokeDashoffset = leng;
});

デモはこちら

イラレでグラフを作成

円グラフをIllustratorで作る時に少しコツがいります。具体的にはパスをカットするところで順番にカットしていかないと、あとで少し面倒になります。

円ツールで円を作成

円ツールを使って円を描きます。ドラッグして作っても、1クリックで大きさを指定して作ってもどちらでもOKです。

Illustratorで円ツールを使って円を作成するアニメーション

アンカーポイント追加

円グラフの割合に応じてパスをアンカーポイントで区切っていきます。
円上のパスにアンカーポイントを作成しているアニメーション

アンカーポイントをカット

追加したアンカーポイントをカットしていくのですが、ポイントがあります。アンカーポイントを1つずつ時計回りにカットしていきます。まとめて選択したあと、いっぺんにカットすると、SVGにしたとき順番がバラバラになって少し厄介です。
円上のアンカーポイントを反時計回りカットしているアニメーション

カットしたらそれぞれの領域に色をつけます。グラフなので塗り分けるのが基本ですが、色をつけないことも可能です。ただ塗り分けないとそれぞれのパスにクラスがつかないので、アニメーションのときに少し面倒です。
円上のパスに色をつけているアニメーション

全体を選択してコピーすればイラレでの作業は終わりです。

コーディング

イラレのベクターデータをコピーしてBrackets、DreamweaverはペーストするだけでSVGのインラインコードが生成されます。(主要なエディタは対応してるかな...?)

それができない場合はイラレからSVGで書き出してコードを貼り付けます。
<svg>タグにid名svg1を振っておきます。以下がそのコードになります。

<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In  -->
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
  x="0px"
  y="0px"
  width="493.3px"
  height="493.3px"
  viewBox="0 0 493.3 493.3"
  style="enable-background:new 0 0 493.3 493.3;"
  xml:space="preserve"
  id="svg1"
>
  <style type="text/css">
    .st0 {
      fill: none;
      stroke: #bdccd4;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st1 {
      fill: none;
      stroke: #ff7bac;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st2 {
      fill: none;
      stroke: #ff1d25;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st3 {
      fill: none;
      stroke: #ff931e;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st4 {
      fill: none;
      stroke: #7ac943;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
    .st5 {
      fill: none;
      stroke: #3fa9f5;
      stroke-width: 64;
      stroke-linejoin: bevel;
      stroke-miterlimit: 10;
    }
  </style>
  <defs></defs>
  <path class="st0" d="M213,34.6c11-1.7,22.2-2.6,33.7-2.6" />
  <path class="st1" d="M173.3,44.8c12.7-4.6,26-8.1,39.7-10.2" />
  <path class="st2" d="M102.7,87.4c20.4-18.4,44.3-33,70.7-42.6" />
  <path class="st3" d="M34.4,214.7c7.5-50.2,32.4-94.8,68.3-127.3" />
  <path
    class="st4"
    d="M175.8,449.3C92,420.1,32,340.4,32,246.7c0-10.9,0.8-21.5,2.4-31.9"
  />
  <path
    class="st5"
    d="M246.7,32c118.6,0,214.7,96.1,214.7,214.7s-96.1,214.7-214.7,214.7c-24.8,0-48.7-4.2-70.9-12"
  />
</svg>

headタグ内にanime.jsを読み込んでおきます。

<script src="./js/anime.min.js"></script>

パスの長さを0にする

anime.jsデフォルトのアニメーションで長さの初期値を0(注)にしてくれるのですが、短いパスなどは微妙見切れる場合があります。保険的に以下の記述を書いておきます。また、こちらの記述はanime.jsを実行する前に0にする場合も有効です。このスクリプトを実行するためにid名を振りました。

//Pathの長さを取得し、オフセットする
//Pathの長さを取得し、オフセットする
var svg1 = document.getElementById("svg1"),
  paths = new Array();
[].slice.call(svg1.querySelectorAll("path")).forEach(function(path, i) {
  paths[i] = path;
  var leng = paths[i].getTotalLength();
  paths[i].style.strokeDasharray = leng + " " + leng;
  paths[i].style.strokeDashoffset = leng;
)};

注:正確にはパスの長さを0にするのではなく、パスを破線にし、破線の間隔をパスの長さと一致させ、長さの分だけ破線をずらしています。ずらした破線を元の位置に戻すことで、線が伸びているようなアニメーションを実現しています。

anime.jsの実装

anime.jsで円グラフのアニメーションを実装していきます。anime.jsそれ自体の使い方については公式ドキュメントやその他のサイトをご参照ください。

//アニメーション
var chart = anime.timeline();
chart
  .add({
    targets: ".st5",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 550,
    delay: 0
  })
  .add({
    targets: ".st4",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 250,
    delay: 0
  })
  .add({
    targets: ".st3",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 100,
    delay: 0
  })
  .add({
    targets: ".st2",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 80,
    delay: 0
  })
  .add({
    targets: ".st1",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 40,
    delay: 0
  })
  .add({
    targets: ".st0",
    strokeDashoffset: [anime.setDashoffset, 0],
    easing: "linear",
    duration: 40,
    delay: 0
  });

storkeDashoffset0にするアニメーションで円グラフが伸びていきます。easingはlinearにすることでパスの境目で速度が変化するのを防ぎます。

targetsがアニメーションをさせるクラス名ですが、ここでは.st5が円グラフの青い部分になり順に4、3、2、1、0と下っていきます。つまり、時計回りにアンカーをカットしていくと、降順でクラス名が振られます。カットする際にまとめて選択してからカットすると、順番がバラバラになるので、面倒になります。

durationの値はアニメーションの時間ですが、割合に応じた時間設定をすると良いです。全体を1秒(=1000)で描画するなら、グラフが45%だったら450、34.2%だったら342といった具合です。割合に応じた設定をすることで、全体的になめらかにアニメーションされます。

完全な円グラフ

ドーナツ型ではなく、完全な円グラフを描く方法です。一つは上記の円グラフと同じように作成して、線の太さを直径と同じすることで一応はできますが、中心付近が綺麗に収束しません。そのため、少し応用して普通に描いた円グラフの上から、白い円グラフを描き、白い円グラフを逆の手順で消して行くことで現れるようにしています。

HTML

<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In  -->
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
  x="0px"
  y="0px"
  width="430px"
  height="430px"
  viewBox="0 0 430 430"
  style="enable-background:new 0 0 430 430;"
  xml:space="preserve"
>
  <style type="text/css">
    .st0 {
      fill: #bdccd4;
    }
    .st1 {
      fill: #ff7bac;
    }
    .st2 {
      fill: #ff1d25;
    }
    .st3 {
      fill: #ff931e;
    }
    .st4 {
      fill: #7ac943;
    }
    .st5 {
      fill: #3fa9f5;
    }
  </style>
  <defs></defs>
  <g>
    <path
      class="st0"
      d="M214.9,215L181.4,2.6C192.5,0.9,203.8,0,215,0v215C214.9,215,214.9,215,214.9,215z"
    />
    <path
      class="st1"
      d="M214.8,214.3L141.6,12.9c12.9-4.7,26.3-8.1,39.8-10.3L214.8,214.3z"
    />
    <path
      class="st2"
      d="M215,215L215,215C215.1,214.9,215.1,214.9,215,215C215.1,214.9,215.1,214.9,215,215z M215,214.9L70.8,55.5 c20.7-18.8,44.6-33.1,70.8-42.7L215,214.9z"
    />
    <path
      class="st3"
      d="M214.9,215.1c0,0,0-0.1,0.1-0.1l0,0C215,215,214.9,215.1,214.9,215.1z M214.9,214.9L2.4,183.1 c7.3-49,31.6-94.3,68.4-127.6L214.9,214.9z"
    />
    <path
      class="st4"
      d="M144,418C57.9,387.8,0,306.3,0,215c0-10.6,0.8-21.4,2.4-31.9L215,214.9l0,0c0,0,0,0,0,0.1c0,0,0.1,0.1,0.1,0.1 c0,0,0,0-0.1,0c0,0.1,0,0.1,0,0.1c0,0,0-0.1,0-0.1L144,418z"
    />
    <path
      class="st5"
      d="M215,430c-24.3,0-48.2-4-71-12L215,215c0,0,0,0-0.1,0c0,0,0,0,0.1,0c-0.1,0-0.1,0-0.1,0c0,0,0,0,0.1,0 c0,0-0.1-0.1-0.1-0.1c0,0,0,0,0.1,0v0c0-0.1,0-0.1,0-0.1c0,0,0,0,0,0V0c29,0,57.1,5.7,83.7,16.9c25.6,10.8,48.6,26.3,68.3,46 c19.7,19.7,35.2,42.7,46,68.3C424.3,157.9,430,186,430,215s-5.7,57.1-16.9,83.7c-10.8,25.6-26.3,48.6-46,68.3 c-19.7,19.7-42.7,35.2-68.3,46C272.1,424.3,244,430,215,430z"
    />
  </g>
</svg>

<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In  -->
<svg
  version="1.1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
  x="0px"
  y="0px"
  width="430px"
  height="430px"
  viewBox="0 0 430 430"
  style="enable-background:new 0 0 430 430;"
  xml:space="preserve"
  id="svg1"
>
  <style type="text/css">
    .st6 {
      fill: none;
      stroke: #ffffff;
      stroke-width: 217;
      stroke-miterlimit: 10;
    }
  </style>
  <defs></defs>
  <path
    class="st6"
    d="M215,107.5c59.4,0,107.5,48.1,107.5,107.5S274.4,322.5,215,322.5S107.5,274.4,107.5,215S155.6,107.5,215,107.5"
  />
</svg>

JavaScript

var leng;
var svg1 = document.getElementById("svg1"),
  paths = new Array();
[].slice.call(svg1.querySelectorAll("path")).forEach(function(path, i) {
  paths[i] = path;
  leng = paths[i].getTotalLength();
  paths[i].style.strokeDasharray = leng + " " + (leng + 1);
  paths[i].style.strokeDashoffset = 0;
});
var chart = anime.timeline();
chart.add({
  targets: ".st6",
  strokeDashoffset: [0, -leng],
  easing: "linear",
  duration: 1000,
  delay: 0
});

デモはこちら

2つのSVG(1つ目が円グラフ本体、2つ目がマスクようの円グラフ)をpositionで重ねておきます。上の白い円グラフをアニメーションで消していくことで、下の円グラフまるで伸びているように見せています。

また、円グラフパスの長さを取得する関数ですが、少し手を加えています。leng変数を関数のスコープから出し、アニメーションでも使えるようにしています。.strokeDasharrayの2つ目の値を+1にしています。これはぴったりだと微妙に見切れる場合があるので1付加しています。

var leng;
var svg1 = document.getElementById("svg1"),
  paths = new Array();
[].slice.call(svg1.querySelectorAll("path")).forEach(function(path, i) {
  paths[i] = path;
  leng = paths[i].getTotalLength();
  paths[i].style.strokeDasharray = leng + " " + (leng + 1);
  paths[i].style.strokeDashoffset = 0;
});
var chart = anime.timeline();
chart.add({
  targets: ".st6",
  strokeDashoffset: [0, -leng],
  easing: "linear",
  duration: 1000,
  delay: 0
});

アニメーションの方のstrokeDashoffsetを[0,-leng]にすることで、逆のアニメーションを実装しています。このとき[0,anime.setDashoffset]にすると、終わり側からアニメーションが始まってしまうので注意してください。

この方法の場合、あくまで白色で隠しているだけなので、背景が単色である場合に限られます。模様がある場合などは隠せないのでこの実装方法では無理です。現在、良い方法を模索中です。