はじめに
スプライトアニメーションとは、コマを画像一枚に敷き詰め、画像内での座標位置を連続で切り替えることで、アニメーションを実現させる手法です。
特にここ数年でどーんと色々なWebサイトで使われるようになっている気がします。私はあまり詳しくはありませんが、ゲーム制作とかでは昔から一般的な手法だったのかもしれません(RPGツクールとかでスプライト画像を見た気もします)。
そんなに難しいものでもなく、アイデア次第では美しく非常にインパクトのある表現も可能です。ここでは動作の基本をまとめてみます。試したことの無い方の参考になれば幸いです。
目次
- アニメーションイメージ
- スプライトアニメーションのメリット・デメリット
- cssだけで表現してみる
- cssとjavascriptで表現してみる
- canvasで表現してみる
アニメーションイメージ
[アニメーション]
※途中でガクッとなるのは、画面をキャプチャして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/
<div class="character"></div>
.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つのパターンが考えられます。
- jsで直接スタイルを修正していきpositionをズラしていく
- jsはあくまでclassの付け替えのみを行ない、スタイルは全てcss側で制御する
前者は数値の計算が不要で調整もしやすいですが、直接スタイルを変更するため重たくなりがちです。
後者はパフォーマンスは良く、jsとスタイルを分離できますが、いかんせん面倒くさいです(cssプリプロセッサやcalc()を使えば、大分手間は軽減するかとは思いますが)。
※1.2でどのくらいパフォーマンス差がでるかは未検証です(良い調べ方が思いつきませんでした......)
全ソース・実際の動きは↓から確認ができます
http://nekoneko-wanwan.github.io/demo/sprite/css-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();
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);
}
.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を削っていくようなアニメーションを作成してみました。
全ソース・実際の動きは↓から確認ができます
http://nekoneko-wanwan.github.io/demo/sprite/canvas/
<canvas id="character" style="display: block; background-color: #7FDBFF;"></canvas>
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^)/