SVGで犬のしっぽをフリフリする

スマホやRetinaを始めとした高解像度ディスプレイへの対応の為、アイコンやロゴなどの画像をSVGで出す機会が増えていると思います。SVGでは一つのリソースでサイズや色が柔軟に使い分けできる他にも、簡単にアニメーションを付けられるのも魅力的ですよね。

このブログではロゴ部分にマウスを乗せると、犬のしっぽがフリフリする様なアニメーションを付けています。SVGにアニメーションを付けるときの方法として、次の3つが一般的です。

  • SMIL
  • CSS
  • JavaScript

今回はJavaScriptを使った方法については書きません。SMILとCSSのそれぞれで犬のしっぽを振るまでの過程について書いてみます。

下準備

アニメーション実装の前にSketchを使ってSVGファイルを用意します。今回はSketchを使いますが、SVGを書き出せるものであればイラレでも何でもOKです。

2つのイラスト(パス)を用意

しっぽを振るために、振る前と後で二つの状態のイラストを用意しました。この時、パスの制御点の総数を変えてしまうとSMILアニメーションがうまくいかない点に注意する必要があります。

しっぽを振る前

しっぽを振った後

SVGの書き出し

用意したイラストをSVGとして書き出します。

SVGのexport

SVGのコードを最適化

Sketchで書きだしたSVGをそのまま使っても良いのですが、レイヤの状態を持っていたり、必要以上に複雑な構造になっています。少し手間ですが、ここではコード上で操作しやすいように最適化を行います。

SVGの最適化には色々な方法があると思いますが、今回GUI操作でさくっと使えるものが良かったためSVGOMGを使っていきます。

SVGOMG
https://jakearchibald.github.io/svgomg/

SVGOMGへアクセス後、右側メニューの「Open SVG」に先ほど作ったSVGをファイルを指定します。ファイルの指定後、以下の様に書き出しの設定を行います。

SVGOMGでの書き出し設定例

項目 On/Off
Show original Off
Compare gzipped On
Prettify code On
Multipass On

上記以外は全てデフォルトの設定にしました。
書き出したSVGは以下の様な感じで最適化されています。

<svg width="395" height="274" viewBox="0 0 395 274" xmlns="http://www.w3.org/2000/svg">
  <title>
    logo_after
  </title>
  <path d="M284.788 43.44c0 6.712 1.487 18.648 1.487 24.615 0 5.968 1.856 8.208-1.488 13.056-3.346 4.85-4.46 5.97 0 11.94s13.04 5.97 17.48 7.83c0 0-24.54-2.98-33.46 0-8.92 2.99-18.582 8.95-33.82 7.46-15.236-1.49-33.446-5.59-50.166-4.47s-40.13 5.97-58.34 20.14c-18.21 14.18-23.4 16.78-32.68 18.59-9.28 1.81-33.45 1.84-45.3 1.81-12.46 1.05-17.03.03-29.37 0-11 1.47-17.56-2.93-18.3-1.81-.74 1.12 9.09 8.27 16.3 11.26 5.71 2.105 10.32 4.06 15.8 5.676 9.31.82 28.9 3.34 41.43 4.15 7.75 0 19.44 3.87 26.86 3.87 7.207 0 7.808 9.06 6.32 13.91s.375 14.577-4.83 19.41c-5.204 4.838-4.83 11.18-1.486 16.03 3.34 4.85 11.52 19.024 12.26 23.126 2.67 4.847 4.09 9.698 3.34 13.055-.745 3.357 1.855 5.97 11.52 6.714 9.66.745 14.77-1.98 19.57-3.896 1.3-3.11 1.71-5.49.5-6.924-4.09-4.85-6.23-3.84-9.667-5.594-5.573-1.867-11.15-7.46-9.29-18.28 1.857-10.82 1.487-12.31 4.46-12.68 2.975-.37 4.46-1.49 8.175 1.86 3.72 3.355 13.195 4.476 13.195 14.36 0 9.88 7.06 13.427 13.94 23.31 6.875 9.886 2.6 11.936 3.16 15.85.555 3.92 9.66 5.97 18.58 5.41 8.92-.56 15.79.378 13.563-6.524-2.23-6.9-7.806-11.75-11.15-13.06-3.342-1.302-11.33-12.865-9.845-20.14 1.487-7.273 9.29-12.495 12.078-20.14 2.788-7.65-.607-11.323 7.43-13.43 8.76-.37 8.363-1.305 18.768.93 10.403 2.24 30.844 15.668 41.993 13.43 11.15-2.24 9.66-2.982 14.68-4.475 5.01-1.49 1.67 5.226.92 6.716-.74 1.493-5.015 14.36.744 15.666 5.76 1.31 28.8 6.53 30.29-1.12 1.35-5.15-2.115-7.827-6.44-11.35-4.087-2.426-4.966-4.5-3.665-6.366 1.3-1.863 12.52-6.34 13.08-6.152.56.19-1.116 10.445.74 20.52 1.86 10.07 2 18.55 6.345 18.93 6.667 2.53 28.817 3.593 33.79.835 3.8-3.425 2.366-11.155-6.58-13.99-6.637-.275-9.653 2.26-11.26-3.54-1.42-7.177-2.754-13.545.74-18.65 2.91-3.576 2.56-9.5 3.34-17.53 3.54-10.63 11.707-16.787 20.44-22.57 8.74-5.78 14.868-11.19 15.426-35.25.56-24.06-2.415-37.67-6.683-46.07-4.272-8.39-14.864-21.445-6.132-30.02 8.737-8.58 23.23-11.75 26.2-17.91 2.978-6.152 7.62-9.7 4.09-12.87-3.53-3.17-4.457-3.354-6.69-3.354-2.227 0 4.65-8.58 4.65-14.36 0-5.786-.557-12.5-6.32-11.19-5.76 1.3-20.255 7.085-26.942 9.51-6.69 2.42-13.01 2.24-15.238 1.303-2.23-.93-4.46-.93-5.76-2.237-1.31-1.3-10.6-1.11-14.872 1.31-5.534 3.14-19.322 6.713-23.597 9.514-4.275 2.8-14.31 17.158-14.31 23.874z" fill-rule="evenodd"/>
</svg>

ちなみに元のSVGは以下の様なコードです。レイヤに引きずられて付与されたg要素(グループ)などがごっそり無くなっています。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="395px" height="274px" viewBox="0 0 395 274" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 3.6.1 (26313) - http://www.bohemiancoding.com/sketch -->
    <title>logo_after</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="01_After" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="Artboard-1" transform="translate(-5.000000, 0.000000)" fill="#000000">
            <path d="M289.787786,43.4386198 C289.787786,50.1516198 291.274703,62.0875185 291.274703,68.0551661 C291.274703,74.0228136 293.130943,76.2630956 289.787184,81.1112057 C286.441019,85.9605229 285.328237,87.0770427 289.787184,93.0471044 C294.24553,99.0171661 302.825981,99.014752 307.268086,100.879038 C307.268086,100.879038 282.726733,97.893404 273.806432,100.879038 C264.886131,103.863466 255.224778,109.829303 239.987485,108.340408 C224.751395,106.846082 206.540868,102.744532 189.820868,103.863466 C173.099064,104.981796 149.683725,109.831113 131.476808,124.004276 C113.265079,138.179853 108.075588,140.782257 98.7946625,142.588043 C89.5137371,144.393829 65.341388,144.430225 53.4953384,144.393829 C41.0371932,145.43912 36.4620936,144.41915 24.1176147,144.393829 C13.1277671,145.868415 6.56875796,141.469109 5.82650232,142.588043 C5.08364518,143.706977 14.9161432,150.854889 22.128188,153.845703 C27.8345368,155.949463 32.4418076,157.90712 37.9282343,159.519882 C47.2308484,160.331528 66.8253701,162.859055 79.3529675,163.66333 C87.1050577,163.66333 98.7946634,167.531967 106.209443,167.531967 C113.412896,167.531967 114.013951,176.595378 112.527034,181.442281 C111.040116,186.292201 112.899966,196.017391 107.696959,200.854034 C102.490943,205.688263 102.865079,212.031303 106.20944,216.878809 C109.553801,221.729937 117.730041,235.904911 118.472898,240.007065 C121.148364,244.85344 122.561319,249.704492 121.819064,253.061294 C121.076808,256.416889 123.676507,259.028941 133.337861,259.774294 C143.000417,260.52025 148.110033,257.794967 152.906463,255.878542 C154.210204,252.773278 154.613945,250.390629 153.407034,248.957933 C149.318613,244.109219 147.182105,245.118034 143.742673,243.366281 C138.16974,241.49958 132.593801,235.903704 134.453049,225.086739 C136.310492,214.267963 135.939966,212.774844 138.913801,212.406695 C141.887635,212.03251 143.372748,210.913576 147.088237,214.267963 C150.80553,217.623558 160.281019,218.744906 160.281019,228.627615 C160.281019,238.512135 167.34147,242.05603 174.217861,251.940549 C181.091846,261.826276 176.819365,263.876448 177.375756,267.79332 C177.932146,271.710192 187.03771,273.762175 195.95741,273.200897 C204.876507,272.63962 211.746883,273.576289 209.519515,266.674386 C207.290342,259.772483 201.714402,254.92377 198.372447,253.615933 C195.028086,252.31232 187.040116,240.748796 188.524628,233.476329 C190.011545,226.201448 197.814252,220.979153 200.602823,213.333708 C203.38959,205.685245 199.994792,202.011787 208.034402,199.906501 C216.793803,199.539053 216.395304,198.601078 226.800116,200.838342 C237.207334,203.07621 257.648237,216.504021 268.797109,214.266756 C279.945981,212.028889 278.459064,211.283536 283.476207,209.791021 C288.492748,208.298505 285.148387,215.013919 284.404327,216.504021 C283.662071,217.99895 279.387786,230.866087 285.147184,232.17151 C290.908387,233.47995 313.948989,238.701642 315.435906,231.053179 C316.786396,225.903385 313.322078,223.225947 308.997101,219.700317 C304.910484,217.27415 304.030857,215.199807 305.331909,213.33371 C306.631157,211.46882 317.850943,206.994893 318.409139,207.180175 C318.968537,207.369078 317.294553,217.622955 319.151996,227.696981 C321.010643,237.767386 321.152448,246.250482 325.495758,246.627411 C332.16408,249.157458 354.314216,250.222981 359.286733,247.465417 C363.088771,244.041598 361.65275,236.312034 352.705048,233.476334 C346.069511,233.202496 343.054359,235.736829 341.449139,229.933642 C340.028788,222.759939 338.697267,216.392235 342.193801,211.284743 C345.100826,207.711453 344.755458,201.785036 345.535756,193.755382 C349.078841,183.125661 357.241019,176.969563 365.97365,171.188404 C374.711094,165.405435 380.838613,159.996047 381.39741,135.940175 C381.955605,111.880078 378.979365,98.2651749 374.711094,89.8707564 C370.436207,81.4805625 359.843725,68.4239194 368.575756,59.845426 C377.310793,51.2657255 391.803425,48.0936022 394.777259,41.9412762 C397.751695,35.7865361 402.396507,32.2426418 398.863876,29.0717255 C395.333049,25.8996022 394.406733,25.7149238 392.175154,25.7149238 C389.946583,25.7149238 396.822974,17.1340163 396.822974,11.3552718 C396.822974,5.57169911 396.266583,-1.14130089 390.502974,0.164725539 C384.74538,1.47014845 370.248537,7.25311761 363.56162,9.67626739 C356.872898,12.1000207 350.552297,11.9153423 348.32553,10.9810868 C346.095756,10.0492454 343.865981,10.0492454 342.56553,8.74382246 C341.254252,7.44202069 331.964628,7.63092378 327.691545,10.05347 C322.15771,13.191796 308.370041,16.76647 304.094553,19.5668225 C299.822071,22.3653643 289.787786,36.7232057 289.787786,43.4386198 Z" id="logo_after"></path>
        </g>
    </g>
</svg>

ここまででアニメーションの下準備は完了したので、SMILとCSS、それぞれアニメーションを付ける方法について書いていきます。

1. SMIL

まずは、SMIL(Synchronized Multimedia Integration Language)を使った方法からです。ロゴを動かすために調べて、初めて知った位の知識なのであまり詳しくはありませんが、XMLベースでアニメーションを宣言的に表現することが可能とのことです。

2枚のイラストを単純に動かすだけなら、後述するCSSを使う方法よりも比較的簡単に実装できます。
しかし、記事を書いた時点(2016年5月7日)で既に廃止予定となっており非推奨扱いです。そのため、今後使う機会は減っていくと思います。

ChromeでSMILを使ったSVGを表示すると、コンソールに以下の様に警告が表示されます。

SVG's SMIL animations are deprecated and will be removed. Please use CSS animations or Web animations instead.

SMILアニメーションは非推奨となってるから、これからは

をSMILの代わりに使ってね、とのことです。


SMILを使ってしっぽを振る場合、以下の様にSVGを作成します。

<svg width="400" height="274" viewBox="0 0 400 274" xmlns="http://www.w3.org/2000/svg">
  <title>Dog</title>
  <path>
    <animate
      attributeName="d"
      repeatCount="indefinite"
      dur="420ms"
      calcMode="spline"
      keySplines=".2,0 .8,.4; .2,.6 .8,1"
      values="
        しっぽを振る前;
        しっぽを振った後;
        しっぽを振る前
      "></animate>
  </path>
</svg>

しっぽを振る前しっぽを振った後に、下準備でそれぞれ書きだしたSVGのd属性の値を割り当てます。
実際にこれをHTMLで表示してみると、以下の様になります。(色味などは別途指定済み)

SMILのループアニメーション

animate要素の属性指定

上記のサンプルでは、animate要素に以下の属性を指定して、アニメーションを実現しています。

属性名
attributeName アニメーション対象の属性名
repeatCount アニメーションの繰り返し回数。整数またはindefinite(ループ)。
dur アニメーションの時間
calcMode 値が変化する際の関数(イージング)。discretelinearpacedsplineなどで指定。
keySplines 各時区間に対する歩調を制御する3次ベジェ関数のリスト。(なんか難しい…)
values 変化する値。それぞれの値を;で区切り指定。

マウスに応じてアニメーションを実行

ループ再生では無く、何らかのアクションに応じてアニメーションを実行する場合でも、JavaScriptなど使用せずに実現出来ます。

以下、マウスオーバでアニメーションを実行する例です。

マウスに応じて動作するSMIL

変更点と参考コードを記載しますが、詳細は別途調べてみてください。

<svg id="dog" width="400" height="274" viewBox="0 0 400 274" xmlns="http://www.w3.org/2000/svg">
  <title>Dog</title>
  <path d="しっぽを振る前">
    <animate
      attributeName="d"
      repeatCount="2"
      dur="420ms"
      calcMode="spline"
      keySplines=".2,0 .8,.4; .2,.6 .8,1"
      begin="dog.mouseover"
      restart="whenNotActive"
      values="
        しっぽを振る前;
        しっぽを振った後;
        しっぽを振る前
      "></animate>
  </path>
</svg>

変更点は以下。

  • svg要素に対し、任意のIDを指定
  • path要素にしっぽを振る前のd属性を指定
  • animate要素
    • begin属性に指定ID.イベントという書式でアニメーションの開始イベントを設定
    • repeatCountindefiniteに変更 (お好みで)

2. CSS

SMILでは、2つのイラスト(d属性値)を丸々変える方法でしたが、CSSでは動かす部分のみ分割し、分割したパーツに対してCSSTransiion、またはCSSAnimationを適用していきます。
GitHub CornersでOctocatが手をヒョコヒョコとさせているのも、この方法をとっています。

GitHub Cornersのアニメーション

パーツを分割

しっぽを振る前のイラストを、

  • 動かす部分 (しっぽ)
  • それ以外 (胴体)

の2種類に分けて分割します。

パーツの分割図

パスを上手く分割出来たら、Sketch上でグループ化しておき、そのグループをSVGとして書き出しておきます。あとは、SVGの書き出しと同じ要領でコードを最適化しておきます。

SVG/CSSの調整

SVGは最終的に以下の様になりました。

<svg class="dog" width="400" height="274" viewBox="0 0 400 274" xmlns="http://www.w3.org/2000/svg">
  <title>Dog</title>
  <g>
    <path class="dog__body" d="M289.793 43.44c0 6.712 1.487 18.648 1.487 24.615 0 5.968 1.856 8.208-1.488 13.056-3.346 4.85-4.46 5.967 0 11.937s13.04 5.968 17.48 7.832c0 0-24.54-2.987-33.46 0-8.92 2.983-18.582 8.95-33.82 7.46-15.235-1.494-33.446-5.595-50.166-4.477-16.722 1.12-40.137 5.968-58.344 20.14-18.212 14.177-17.463 52.592-18.95 57.44-1.487 4.85.373 14.574-4.83 19.41-5.206 4.835-4.832 11.178-1.487 16.026 3.344 4.85 11.52 19.025 12.263 23.127 2.676 4.846 4.09 9.697 3.346 13.054-.742 3.357 1.858 5.97 11.52 6.714 9.662.746 14.77-1.98 19.568-3.895 1.303-3.107 1.707-5.49.5-6.922-4.088-4.85-6.225-3.84-9.664-5.592-5.573-1.866-11.15-7.462-9.29-18.28 1.858-10.818 1.487-12.31 4.46-12.68 2.975-.373 4.46-1.492 8.175 1.862 3.718 3.356 13.193 4.477 13.193 14.36 0 9.884 7.06 13.428 13.937 23.313 6.874 9.886 2.602 11.936 3.158 15.853.557 3.917 9.663 5.97 18.583 5.408 8.92-.56 15.79.376 13.562-6.526-2.23-6.902-7.805-11.75-11.147-13.058-3.345-1.304-11.333-12.867-9.848-20.14 1.487-7.275 9.29-12.497 12.078-20.142 2.787-7.65-.608-11.322 7.432-13.427 8.76-.368 8.36-1.306 18.765.93 10.408 2.24 30.848 15.667 41.997 13.43 11.15-2.238 9.662-2.983 14.68-4.476 5.016-1.49 1.672 5.224.928 6.714-.743 1.495-5.017 14.362.742 15.668 5.762 1.308 28.802 6.53 30.29-1.12 1.35-5.15-2.115-7.826-6.44-11.352-4.086-2.426-4.966-4.5-3.665-6.366 1.3-1.865 12.52-6.34 13.077-6.154.56.19-1.114 10.443.743 20.517 1.86 10.07 2 18.553 6.344 18.93 6.67 2.53 28.82 3.596 33.792.838 3.802-3.423 2.366-11.153-6.582-13.99-6.635-.273-9.65 2.262-11.256-3.54-1.42-7.175-2.752-13.543.745-18.65 2.906-3.574 2.56-9.5 3.34-17.53 3.544-10.63 11.706-16.785 20.44-22.567 8.736-5.783 14.864-11.192 15.423-35.248.558-24.06-2.418-37.675-6.687-46.07-4.275-8.39-14.867-21.446-6.135-30.025 8.736-8.58 23.23-11.75 26.202-17.904 2.975-6.153 7.62-9.697 4.087-12.868-3.532-3.172-4.458-3.357-6.69-3.357-2.228 0 4.648-8.58 4.648-14.36 0-5.783-.556-12.496-6.32-11.19-5.757 1.305-20.254 7.088-26.94 9.51-6.69 2.425-13.01 2.24-15.237 1.306-2.23-.93-4.46-.93-5.76-2.236-1.31-1.302-10.6-1.113-14.873 1.31-5.534 3.138-19.322 6.712-23.597 9.513-4.273 2.798-14.307 17.156-14.307 23.872z"/>
    <path class="dog__tail" d="M134.126 177.854c2.897-2.007 10.125-4.04 12.396-6.605 7.365-8.314 7.183-28.052 2.49-37.908-2.053-4.314-2-10.104-6.715-10.67-3.957-.476-12.895 3.98-13.418 3.847-1.234-.313-21.684 33.94-22.794 35.103-7.135 7.478-21.093 23.51-40.022 31.655-18.928 8.142-26.928 16.646-34.013 22.944-4.584 5.584-7.632 13.83-8.037 15.11-.405 1.28 8.292-7.333 16.847-11.4 11.53-5.797 11.524-4.924 21.468-8.18 11.977-4.542 29.13-9.463 34.484-13.714 9.03-6.658 10.08-5.885 22.57-12.863 1.823-1.214 9.453-3.656 14.744-7.32z"/>
  </g>
</svg>

胴体としっぽのパスはgでグルーピングされ、それぞれのpathにはCSSから操作しやすいようにクラスを付与しています。

あとはCSSAnimationでしっぽの部分をフリフリさせれば完成です。

.dog__tail {
  transform-origin: 100% 0%;
  animation: dog-tail 500ms ease-in-out 0s infinite alternate;
}

@keyframes dog-tail {
  0% {
    transform: translate(0, 0) rotate(0deg);
  }
  50% {
    transform: translate(10%, 0%) rotate(20deg);
  }
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
}

transform-originで基準点をずらし、しっぽを回転させることでフリフリに見せています。
transform: translateの指定は、回転した際にしっぽの切れ目が不自然に見えてしまうことを防ぐために使用しています。

そして、これをHTMLで実際に表示したものが以下。

CSSでしっぽを振るアニメーション

まとめ

  • SIMLは非推奨となっている
  • 今後はCSS、Web Animationsを使ったアニメーションを使用する
  • 単純な動きであれば、CSSで充分
  • 複雑なパスアニメーションが必要になった場合は何らかのライブラリに頼るのが懸命っぽい
  • 犬のしっぽはSVG+CSSで簡単に振れる
Newer Post JavaScriptで指定した色をある基準色に分類する Older Post Gitの差分ファイルをZIPにまとめるCLIツールを作った