SVG+CSSでキャラクターアニメーションを作ろう!

ブログメインビジュアル こんにちは、本田です!
今回は、私が大好きなCSSアニメーションを使って、動くキャラクターを作ってみます。
サイトにちょっとした動きがあると目を惹かれますし、アニメーションがあると可愛いと思うので、よければ試してみてください!

目次

準備

パスの作成

まずは動かす元になるキャラクターのパスを作成します。 パスを作成する際は、「頭」「胴体」「脚」のように大まかにパーツごとに分けておくと、そのフォルダごとに出力してくれるので見やすくなります。
この時、どのパーツを動かすかなども一緒に考えておくとアニメーションを作りやすいです。 今回は、「頭」「胴体」「両腕」「ピアス」を動かす想定で作成しました。

下絵を描いたら、それに沿ってベクターツールで形を作りましょう!
今回は AFFINITY Designerというツールを使用しました。
買い切り+比較的安価+高機能なので、「デザインツールを使いたいけど月額制のツールは躊躇ってしまう…」という方にはとてもおすすめです。

他にも GIMP や Figma などでもベクターツールの使用と SVG 出力ができるので、そちらもおすすめです!!
パスが完成したら SVG 形式でエクスポートします。

実装する

HTML の記述

続いて実装に入ります!

<body>
  <svg width="100%" height="100%" viewBox="0 0 600 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
    <g id="leg">
      <path d="M235.813,539.262C235.813,539.262 206.851,690.777 213.741,701.906C220.63,713.035 270.631,715.388 273.411,704.382C276.192,693.375 270.409,624.081 275.183,548.503C279.956,472.925 235.813,539.262 235.813,539.262Z" style="fill:rgb(226,197,194);stroke:rgb(226,197,194);stroke-width:4.64px;" />
      <path d="M325.257,519.613C326.106,588.135 326.022,683.87 322.845,704.745C321.024,716.712 371.591,708.5 378.136,707.863C384.681,707.225 372.747,564.167 368.835,548.797C364.922,533.428 325.04,502.132 325.257,519.613Z" style="fill:rgb(226,197,194);stroke:rgb(226,197,194);stroke-width:4.64px;" />
      <path d="M212.756,689.538C212.756,689.538 245.108,696.148 273.19,693.729C275.306,702.995 279.193,709.51 277.889,715.08C276.318,721.793 211.083,718.12 211.083,709.393C211.083,700.667 212.756,689.538 212.756,689.538Z" style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;" />
      <path d="M324.986,695.102C324.986,695.102 318.815,711.057 319.081,714.222C319.437,718.475 381.999,717.973 381.778,712.604C381.368,702.685 382.815,692.014 379.366,693.092C358.131,698.583 324.986,695.102 324.986,695.102Z" style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;" />
    </g>
    <g id="body">
      <path d="M255.941,378.568C255.941,378.568 266.989,335.082 243.244,362.88C219.499,390.678 206.015,398.007 212.831,414.529C219.038,429.575 229.76,439.851 251.635,403.376C259.571,390.143 255.006,378.372 255.941,378.568Z" style="fill:rgb(226,197,194);stroke:rgb(226,197,194);stroke-width:4.64px;" />
      <path d="M340.71,377.147C337.586,370.532 343.111,341.21 358.894,364.424C365.144,373.617 372.605,386.736 382.516,398.056C408.55,427.791 386.675,456.593 348.314,409.847C335.227,393.9 347.53,391.585 340.71,377.147Z" style="fill:rgb(226,197,194);stroke:rgb(226,197,194);stroke-width:4.64px;" />
      <path d="M216.965,392.712C216.497,392.908 164.3,455.49 139.75,467.183C123.23,490.681 154.317,495.961 173.067,484.71C170.975,492.824 169.474,505.031 192.038,487.186C205.055,470.169 243.244,425.021 244.229,420.829C245.213,416.637 217.432,392.516 216.965,392.712Z" style="fill:rgb(245,234,233);stroke:rgb(245,234,233);stroke-width:4.64px;" />
      <path d="M357.836,421.025L378.432,391.536C378.432,391.536 412.548,439.14 447.871,461.79C466.589,473.793 451.34,500.791 424.323,485.641C426.611,492.186 425.578,512.949 406.311,488.411C387.044,463.874 357.836,421.025 357.836,421.025Z" style="fill:rgb(245,234,233);stroke:rgb(245,234,233);stroke-width:4.64px;" />
      <path d="M299.323,332.93C299.323,332.93 277.771,341.8 251.537,354.185C239.172,399.578 209.966,526.751 202.053,564.281C228.293,581.27 341.373,587.728 397.477,568.62C383.476,521.775 352.054,356.876 352.029,357.121C353.783,353.773 299.323,332.93 299.323,332.93Z" style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;" />
      <path d="M271.911,335.629L262.782,347.763C262.782,347.763 273.904,380.855 274.347,380.243C274.79,379.63 301.562,363.77 301.562,363.77C301.562,363.77 328.432,379.777 328.26,379.63C328.088,379.483 339.234,349.969 339.234,349.969L325.578,334.967L271.911,335.629Z" style="fill:rgb(245,234,233);stroke:rgb(245,234,233);stroke-width:4.64px;" />
      <path d="M275.602,339.424C275.602,339.424 296.664,391.124 325.209,339.938C295.213,311.851 274.692,338.414 275.602,339.424Z" style="fill:rgb(223,184,180);stroke:rgb(223,184,180);stroke-width:4.64px;" />
    </g>
    <g id="head">
      <path d="M196.427,233.642C191.535,264.054 187.27,295.416 161.064,313.555C161.064,313.555 168.423,323.919 206.045,320.247C210.474,324.513 227.551,341.525 253.265,324.243C292.114,298.133 198.98,217.776 196.427,233.642Z" style="fill:rgb(204,139,79);stroke:rgb(204,139,79);stroke-width:4.64px;" />
      <path d="M411.902,227.784C411.902,227.784 411.976,305.515 440.814,317.281C426.543,329.072 409.81,328.9 397.95,321.963C387.886,326.964 368.343,339.272 349.082,321.571C330.516,304.51 412.025,227.784 411.902,227.784Z" style="fill:rgb(204,139,79);stroke:rgb(204,139,79);stroke-width:4.64px;" />
      <g transform="matrix(1,0,0,1,0.393702,9.19241)">
        <path d="M202.452,226.624C202.452,226.624 174.967,216.819 172.334,243.979C169.701,271.14 196.375,296.315 215.666,284.45C234.957,272.586 202.009,226.869 202.452,226.624Z" style="fill:rgb(223,184,180);stroke:rgb(223,184,180);stroke-width:4.64px;" />
      </g>
      <g transform="matrix(1,0,0,1,-3.14962,4.58395)">
        <path d="M401.223,234.713C401.223,234.713 414.805,223.609 424.648,231.943C434.49,240.278 430.758,313.812 379.963,286.951C368.791,281.043 401.223,234.713 401.223,234.713Z" style="fill:rgb(223,184,180);stroke:rgb(223,184,180);stroke-width:4.64px;" />
      </g>
      <path
        d="M208.013,278.751C219.469,307.054 231.466,317.419 248.196,328.411C263.447,338.431 280.318,340.158 298.307,341.049C320.789,342.163 341.733,339.771 360.287,326.036C396.019,299.584 402.268,249.455 404.336,208.713C405.206,191.565 403.324,171.917 395.478,156.353C381.876,129.37 352.722,121.313 324.773,118.911C285.648,115.549 244.906,116.854 215.858,146.517C180.667,182.453 196.461,250.209 208.013,278.751Z"
        style="fill:rgb(226,197,194);stroke:rgb(226,197,194);stroke-width:4.64px;"
      />
      <path d="M363.87,253.024L357.399,261.702C357.399,261.702 349.328,243.072 338.083,258.123C326.838,273.174 328.649,291.295 345.661,289.206C361.631,287.245 359.22,270.331 357.399,261.702" style="fill:rgb(31,30,61);stroke:rgb(31,30,61);stroke-width:4.64px;" />
      <path d="M236.926,250.892L245.71,259.716C245.71,259.716 256.517,241.903 267.881,262.56C278.373,281.634 265.026,292.76 257.915,291.682C250.804,290.603 238.993,289.255 245.71,259.716" style="fill:rgb(31,30,61);stroke:rgb(31,30,61);stroke-width:4.64px;" />
      <path d="M289.534,306.904C300.288,319.051 307.177,312.027 312.615,306.071" style="fill:none;stroke:rgb(177,41,59);stroke-width:4.64px;" />
      <path
        d="M301.838,102.269C301.838,102.269 285.671,89.203 248.639,104.941C211.606,120.678 184.667,178.825 184.588,214.294C184.514,247.411 195.242,281.099 225.878,286.656C221.202,279.271 212.812,269.08 216.306,222.187C233.161,222.187 384.884,223.805 384.884,223.805C384.884,223.805 387.69,257.758 383.282,267.419C376.697,281.852 371.4,285.137 371.4,285.137C371.4,285.137 419.852,278.412 417.93,220.177C415.937,159.777 404.692,120.677 347.285,101.558C322.198,93.203 301.198,101.584 301.838,102.269Z"
        style="fill:rgb(204,159,79);stroke:rgb(204,159,79);stroke-width:4.64px;"
      />
      <path
        d="M227.632,155.815C227.632,155.815 249.727,130.855 268.317,127.602C286.906,124.35 329.71,123.829 340.804,128.34C360.482,136.342 361.365,135.883 380.434,156.137C371.944,151.28 360.003,150.069 330.878,145.103C320.018,143.251 288.182,142.947 270.319,145.515C247.551,148.788 227.632,155.815 227.632,155.815Z"
        style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;"
      />
      <g transform="matrix(1,0,0,1,8.66144,18.3848)">
        <path d="M191.97,272.863L177.034,314.389C176.985,314.242 207.226,314.756 207.226,314.756L191.97,272.863Z" style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;" />
      </g>
      <g transform="matrix(1,0,0,1,-5.43801,14.8059)">
        <path d="M403.388,279.212L388.944,319.365L417.463,319.512L403.388,279.212Z" style="fill:rgb(180,0,54);stroke:rgb(180,0,54);stroke-width:4.64px;" />
      </g>
      <path d="M336.877,236.535C342.274,231.801 349.397,235.297 354.351,240.251" style="fill:none;stroke:rgb(31,30,61);stroke-width:4.64px;" />
      <path d="M247.991,237.702C252.472,233.627 262.838,231.913 267.121,236.196" style="fill:none;stroke:rgb(31,30,61);stroke-width:4.64px;" />
    </g>
  </svg>
</body>

先ほど作成した SVG ファイルからパスをコピーして、キャラクターを載せたい場所にペーストします。
SVG ファイルから<svg>〜</svg>をコピーしてきます。

今回はキャラクターのみの実装なので、body直下に直接貼り付けました。
パスを直接貼るとどうしてもコードが見にくくなってしまうので、そろそろ直接貼り付けなくてもアニメーションをつけられるいい方法を探したいです…

準備の段階で書いたように、パーツを大まかにフォルダごとに分けておくと、上記の HTML のように<g>タグで囲って出力してくれます。

CSS の記述

上下に動くアニメーションを作る

まずは体を上下に揺らすアニメーションを実装します。
上下に揺れるアニメーション

最初の段階から少しコードを整えました。長くなるのでパスの内容は省略して記載します。

<div class="leg">
  <svg width="100%" height="100%" viewBox="0 0 600 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
    <g>
      <!--パス-->
    </g>
  </svg>
</div>
<div class="body">
  <svg width="100%" height="100%" viewBox="0 0 600 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
    <g id="body">
      <g class="right-arm">
        <!--右腕のパス-->
      </g>
      <g class="left-arm">
        <!--左腕のパス-->
      </g>
    </g>
  </svg>
</div>
<div class="head">
  <svg width="100%" height="100%" viewBox="0 0 600 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
    <g id="head">
      <!--パス-->
      <g class="earling-right" transform="matrix(1,0,0,1,8.66144,18.3848)">
        <!--ピアスのパス-->
      </g>
      <g class="earling-left" transform="matrix(1,0,0,1,-5.43801,14.8059)">
        <!--ピアスのパス-->
      </g>
      <!--パス-->
    </g>
  </svg>
</div>

「頭」「胴体」「脚」を分離しました。全ての SVG タグの width、height は揃えて、クラスにposition: absoluteを記述するとパーツが正しい位置に表示されます。
ベクターで構成されているので、どれだけ拡大しても画質が悪化しないのが SVG のいいところだと思います。

.head,
.body,
.leg {
  position: absolute;
}


アニメーションの作成には@keyframesプロパティを使用します。 一連のアニメーションを、0%〜100%までの区間で好きなように区切って動かします。

例えば、全部で10秒のアニメーションを作成する場合、
10%(1秒経過)の段階:X軸方向に50px移動する
40%(4秒経過)の段階:Y軸方向に70px移動する
(省略)
100%(10秒経過)の段階:元の位置に戻る
のようにして使います。

移動量はtransformプロパティで指定します。
下記がキャラクターの頭と胴体を動かすための`@keyframes`です。

@keyframes anime_head {
  0% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(10px);
  }
  100% {
    transform: translateY(0);
  }
}
@keyframes anime_body {
  0% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(7px);
  }
  100% {
    transform: translateY(0);
  }
}

transform: translateY()を用いて 50%地点で Y 方向に少し移動し、100%地点で元の位置に戻るように指定しています。 この時、頭と胴体の移動量は少しずらした方が見た目が良くなると思います。

どのように動かすかを指定したら、どのパーツに対してその動きをつけるか、何秒で動かすかなどを指定します。
頭と胴体の CSS に、以下のように付け加えてください。

.head {
  animation: anime_head 2s ease-in-out;
  animation-iteration-count: infinite;
}
.body {
  animation: anime_body 2s ease-in-out;
  animation-iteration-count: infinite;
}

animationには先ほどの@keyframesにつけたアニメーション名(anime_head、anime_body)と実施する秒数、イージングを指定します。
animation-iteration-countにはアニメーションを実施する回数を指定します。

腕を振る、ピアスが揺れるアニメーションを作る

続いて、体の揺れに合わせて腕とピアスが動くアニメーションを作ります。この動作をつけることで、より生き生きとした動きになる気がします。

完成イメージはこんな感じです。
パーツを揺らすアニメーション

基本は先ほどと同じ要領で作ります。

@keyframes anime_rightarm {
  0% {
    transform: rotate(0);
  }
  50% {
    transform: rotate(3deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes anime_leftarm {
  0% {
    transform: rotate(0);
  }
  50% {
    transform: rotate(-3deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes anime_earlingright {
  0% {
    transform: matrix(1, 0, 0, 1, 8.66144, 18.3848) rotate(0);
  }
  50% {
    transform: matrix(1, 0, 0, 1, 8.66144, 18.3848) rotate(0.5deg);
  }
  100% {
    transform: matrix(1, 0, 0, 1, 8.66144, 18.3848) rotate(0);
  }
}
@keyframes anime_earlingleft {
  0% {
    transform: matrix(1, 0, 0, 1, -5.43801, 14.8059) rotate(0);
  }
  50% {
    transform: matrix(1, 0, 0, 1, -5.43801, 14.8059) rotate(-0.5deg);
  }
  100% {
    transform: matrix(1, 0, 0, 1, -5.43801, 14.8059) rotate(0);
  }
}

今回は回転の動作を加えたいのでrotate()プロパティを用いました。

変形の中心になるポイントを指定することができるので、transform-originを用いて回転の軸を指定します。

.right-arm {
  animation: anime_rightarm 2s ease-in-out;
  animation-iteration-count: infinite;
  transform-origin: 20% 35%;
}
.left-arm {
  animation: anime_leftarm 2s ease-in-out;
  animation-iteration-count: infinite;
  transform-origin: 55% 35%;
}
.earling-right {
  animation: anime_earlingright 2s ease-in-out;
  animation-iteration-count: infinite;
  transform-origin: top center;
}
.earling-left {
  animation: anime_earlingleft 2s ease-in-out;
  animation-iteration-count: infinite;
  transform-origin: top center;
}


まとめ

いかがでしたでしょうか?
よく使われるtranslate()rotate()プロパティを組み合わせるだけでも、工夫次第で多様なアニメーションを作ることができるのでぜひ試してみてください!
最後まで読んでいただきありがとうございました!

この記事を書いた人 honda 2020年新卒 踊るタイプのフロントエンドエンジニアです。
TOP