はじめよう！スプライトアニメーション


# はじめに
スプライトアニメーションとは、コマを画像一枚に敷き詰め、画像内での座標位置を連続で切り替えることで、アニメーションを実現させる手法です。

特にここ数年でどーんと色々なWebサイトで使われるようになっている気がします。私はあまり詳しくはありませんが、ゲーム制作とかでは昔から一般的な手法だったのかもしれません（RPGツクールとかでスプライト画像を見た気もします）。

そんなに難しいものでもなく、アイデア次第では美しく非常にインパクトのある表現も可能です。ここでは動作の基本をまとめてみます。試したことの無い方の参考になれば幸いです。



# 目次
- アニメーションイメージ
- スプライトアニメーションのメリット・デメリット
- cssだけで表現してみる
- cssとjavascriptで表現してみる
- canvasで表現してみる



# アニメーションイメージ
[ソース画像]
![images.png](https://qiita-image-store.s3.amazonaws.com/0/58436/baeb71ff-9f84-cb4d-74b8-38017a6cdaa8.png)


[アニメーション]
※途中でガクッとなるのは、画面をキャプチャしてgifアニメを作成したためです
![taro.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/c769518a-16da-06f8-bb2a-8ea8e7587c20.gif)



> 画像素材は、以下利用させていただきました
> http://blawat2015.no-ip.com/~mieki256/diary/201402.html
> ※キャラクターはかの有名な大崎一番太郎です（CV:山口勝平）



# スプライトアニメーションのメリット・デメリット
## メリット
**パフォーマンスに優れる**
全コマをまとめるためサイズは大きくなりますが、ファイルが一つで済むので、リクエスト数を劇的に減らすことができます。

**色々なデータが使える**
gif, jpg, png, svg...様々な拡張子が使えます。特に透過データが使えるのが一番のメリットかもしれません

**動作の調整が容易**
gifアニメは一旦作ってしまうと調整が大変ですが、スプライトではスピードなどアニメーションの調整は簡単です。javascriptと組み合わせて複雑な表現も可能です。



## デメリット
**スプライト画像を作るのが面倒くさい**
画像のアニメーションを作る上では避けられない気もしますが、画像自体の調整や修正など面倒くさいです。動的にスプライト画像を生成する仕組みを入れたり、共通化を意識してデザインしたりしないと、かなりしんどいかもしれません。

> 画像サイズに制限がある古ーいスマホのようなものには対応できないこともあります。



# cssだけで表現してみる
単純なものであればcss3のanimationのみで実現可能です。

> 全ソース・実際の動きは↓から確認ができます
> http://nekoneko-wanwan.github.io/demo/sprite/css/


```sprite.html
<div class="character"></div>
```

```sprite.css
.character {
  margin: 100px auto 0;
  /* 画像置換 */
  background: url(../images.png) 0 0 no-repeat #ccc;
  height: 0;
  overflow: hidden;
  padding-top: 64px;
  width: 64px;
  /* アニメーション */
  animation: sprite1 0.5s steps(7, start) infinite;
}

@keyframes sprite1 {
  0%   {background-position: 0 0;}
  100% {background-position: -448px 0;}
}
```

- 要素に対して画像置換を行ない、背景にスプライト画像を指定します
- 一コマ64×64の画像が横に8個並んだ画像を使用しています
- animationのtiming-functionにsteps()を設定し、等間隔に出力されるようにします

## steps()について

> `steps(number_of_steps, direction)`
> **number_of_steps**
> 　厳密に正の <整数値> で、ステップ関数を構成する等間隔の段数を示すものです。
> **direction**
> 　関数が 左連続か右連続 かを表すキーワードです:
> 　- start は左連続関数を表し、従ってアニメーションの開始時に最初のステップが発生します。
> 　- end は右連続関数を表し、従ってアニメーションの完了時に最後のステップが発生します。
> > 引用: https://developer.mozilla.org/ja/docs/Web/CSS/timing-function

ここでは `0から-448pxまでの間を7回` 等間隔ごとに`background-position`をずらすようにしています。結果的に64px単位でコマが切り替わっていくようになります。
つまり**ズラす回数をstepsに、ズラす回数×コマ幅（縦にズラす場合は高さ）の値をkeyframesの100%**に指定すれば、期待通りの動作になるかと思います。



# cssとjavascriptで表現してみる
javascriptと組み合わせることで、cssのanimation非対応ブラウザに対応・複雑な表現を行うことができます。この場合、大きく分けて2つのパターンが考えられます。

1. jsで直接スタイルを修正していきpositionをズラしていく
1. jsはあくまでclassの付け替えのみを行ない、スタイルは全てcss側で制御する

前者は数値の計算が不要で調整もしやすいですが、直接スタイルを変更するため重たくなりがちです。
後者はパフォーマンスは良く、jsとスタイルを分離できますが、いかんせん面倒くさいです（cssプリプロセッサやcalc()を使えば、大分手間は軽減するかとは思いますが）。

※1.2でどのくらいパフォーマンス差がでるかは未検証です（良い調べ方が思いつきませんでした......）


> 全ソース・実際の動きは↓から確認ができます
> http://nekoneko-wanwan.github.io/demo/sprite/css-js/


```直接スタイル変更.js
function changePosition() {
  var $target = $('.character');
  var ROW     = 64;
  var LIMIT   = 7;
  var i       = 0;

  setInterval(function(){
    // 'px ' ←半角スペース必須
    var move = -(ROW * i) + 'px ' + 0 +'px';
    $target.css({
      "background-position": move
    });
    if (i === LIMIT) {
      i = 0;
    } else {
      i++;
    }
  }, 70);
}
changePosition();
```


```classの付け替え.js
function changeClass() {
  var $target = $('.character');
  var LIMIT   = 7;
  var i       = 0;

  setInterval(function(){
    $target.attr('class', 'is-position-' + i).addClass('character');
    if (i === LIMIT) {
      i = 0;
    } else {
      i++;
    }
  }, 70);
}
```

```classの付け替え.css
.is-position-0 { background-position: 0 0;}
.is-position-1 { background-position: -64px 0;}
.is-position-2 { background-position: -128px 0;}
.is-position-3 { background-position: -192px 0;}
.is-position-4 { background-position: -256px 0;}
.is-position-5 { background-position: -320px 0;}
.is-position-6 { background-position: -384px 0;}
.is-position-7 { background-position: -448px 0;}
```



# canvasで表現してみる
canvas内で使用することで、パフォーマンスを保ちつつ複雑な表現が可能となります（DOM操作を行う必要が無い）。ここでは大崎一番太郎が私のHPを削っていくようなアニメーションを作成してみました。

[アニメーションイメージ]
![taro_move.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/84ec8368-8735-18a3-a725-2df56f99b2ea.gif)



> 全ソース・実際の動きは↓から確認ができます
> http://nekoneko-wanwan.github.io/demo/sprite/canvas/



```canvas.html
<canvas id="character" style="display: block; background-color: #7FDBFF;"></canvas>
```

```canvas.js
function character() {
    var canvas    = document.getElementById('character');
    var context   = canvas.getContext('2d');
    var img       = new Image();
    var row       = 64;  // 一コマの横幅
    var col       = 64;  // 一コマの縦幅
    var step      = 0;   // コマ切り替えの段階
    var limit     = 7;   // コマ切り替えの上限
    var move      = 0;   // 横にスライドさせる位置
    var ax        = 3;   // 横の移動量
    canvas.width  = 500;
    canvas.height = 80;

    img.src = '../images.png?' + new Date().getTime();

    function render() {
      /* 矩形とテキストの描画 */
      context.clearRect(0, 0, 500, 100);
      context.fillStyle = "rgb(200, 0, 0)";
      context.fillRect(move + row - 5, 20, 500 - move + row, 40);
      context.fillStyle = "rgb(175, 0, 0)";
      context.fillRect(move + row - 5, 55, 500 - move + row, 5);
      context.fillStyle = "rgb(255, 255, 255)";
      context.font = '16px/2 sans-serif';
      context.fillText('MY HP', move + row + 5, 45);

      /* キャラクター画像の描画 */
      context.drawImage(img, row * step, 0, row, col, move, 0, row, col);
      if (step < limit) {
        step++;
      } else {
        step = 0;
      }
      if (move < 500 - row) {
        move = move + ax;
      } else {
        move = 0;
      }
      setTimeout(render, 50);
    }

    img.onload = function() {
      render();
    };
}

character();
```



大崎一番太郎がうごいた＼（^0^）／