2012年6月にサービスを開始したスマートフォン向けソーシャルゲーム ULTIMATE RACERアルティメットレーサー)でデベロッパーをしている横田と申します。

今回はこの弊社提供のサービスULTIMATE RACERの開発時に、画面内のアニメーションをjQueryのanimate処理からcssアニメーションに切り替えた経緯について、自分なりの考察ともにご紹介したいと思います
対象としてはデザイナー、マークアップエンジニアでJSやcssでアニメーションの実装を始めたばかりの方が最適だと思います。

また本稿はjQueryのバージョン1.7.1を使用した場合を想定して記述しています。

ULTIMATE RACER(アルティメットレーサー)のご紹介

最初に簡単ですがULTIMATE RACERのご紹介をさせていただきます。
ULTIMATE RACERはスマートフォン向けのソーシャルゲームで、毎週入れ替わるグループ内のプレイヤー同士でレースをして順位を競うゲームです。
そのレースに使用するマシンが3D(3Dプリレンダリンググラフィック)になっているのが特徴のゲームです。
よろしければ一度ぜひお試しください。

1 pixel|サイバーエージェント公式クリエイターズブログ

ULTIMATE RACER
$1 pixel|サイバーエージェント公式クリエイターズブログ-UR_URL

※スマートフォン専用サービスとなっています。スマートフォンでアクセスしてください。


またもう一つの特徴が画面のUIにアニメーションを多用している点です。

マイカート(マイページ)のUI例




今回はこのUIアニメーションの開発時のお話をさせていただきたいと思います。


jQueryからCSSアニメーションに切り替え

ULTIMATE RACER開発初期の主にPC画面で開発している期間はUIアニメーションをjQueryのanimateを使用して制作を進めていました。
PCではあまり問題を感じなかったのですが、いざ実機確認するとストレスを感じる程に辛そうにアニメーションするのが分かりました。
ユーザーが使いやすくなるようにアニメーションさせているのに、これでは逆に使いにくくゲームの楽しささえ損ねてしまうことになりかねません。

そこでjQueryによるアニメーションをほかのアニメーション処理に差し替えることにし、その際に導入においての学習コストが軽いCSSによるアニメーションを選択しました。

まずはjQueryによるアニメーションと、CSSによるアニメーションについて簡単にご紹介させていただきます。


jQueryのanimateによるアニメーション

jQueryにはanimateというメソッドがあり、簡単に要素をアニメーションさせながら変化させることができます。

例えば、leftの値が0の要素をanimateで{left: 1000}とした場合、leftの値が0pxから1000pxに徐々に変化し要素は右へアニメーションしながら移動していきます。
このときjQueryの内部処理としては、setIntervalというJavaScriptのタイマー機能を使って毎1ミリ秒毎に処理を繰り返して徐々に変化させているのです。

しかしこの一回ごとの処理は、様々な要因で処理が追いつかなくなった場合、実行が後回しになってしまいスキップされたように見えてしまいます。(※setInterval()の細かな仕様ついてはここでは触れません)
そのため処理能力が低いと意図したアニメーションを再現することはできません。
これがPCブラウザでは滑らかに見えても実機ではぎこちなくなる原因となります。

以下に用意したサンプルをPCとiPhone/Android実機でそれぞれでご確認ください。

jQuery animeの処理のステップ確認 - jsdo.it - http://jsdo.it/perma/59n1
var box = $('#box');

function countStep(){$('span').append( "|")};//spanタグにパイプを挿入

$('#start').click(function() {

box.css('left', 0).stop();//css初期化
$('span').empty();//ゲージ初期化

box.animate({ left: 1000 },{//animateメソッドで左に移動
duration: 1000,
step: function() {
countStep();//ステップごとにゲージ増加
}
});

box.animate({ left: 0 },{//animateメソッドで元の位置へ
duration: 1000,
step: function() {
countStep();
}
});

});
$1 pixel|サイバーエージェント公式クリエイターズブログ-UR_jsdo_59n1
このサンプルではanimateの引数のstepで、アニメーション中に値が書き換わった(処理が行われた)ごとにゲージが伸びるようにしたものです。
これでステップ数をPCと実機で比較してみると、実機ではPCよりステップ数がかなり少なくなります。

つまりjQueryのanimateはかなり重い処理を行っており、PCに比べて処理能力の低いスマートフォンのアニメーションには向かないと言えます。
ULTIMATE RACER開発時にぶつかったのがこの点です。


CSSによるアニメーション

そこで選択したのがcss3から追加されたcssのアニメーションです。
このcssを使ったアニメーションはanimationsとtransitionsの二つのプロパティがあります。
どちらもJSで要素を操作するのではなく、ブラウザ自身のエンジンを使用するため挙動が比較的軽いという特徴が共通しています。
また、どちらもcssのみでは(hover等の疑似要素でしか)スタート/エンドのタイミングを制御が難しくある程度複雑なアニメーションの場合はJSでの制御が必要になります。

以下に、上記のjQueryのanimateで行ったものと同じ動きを、animationsとtransitionsの二つのプロパティで再現したものをご用意しました。

animations

特徴してはkeyframesを使って細かい設定が出来る点と、ループ処理が手軽に出来る点です。

CSS animations - jsdo.it - http://jsdo.it/perma/h9Fw
#box { 
width: 100px;
height: 100px;
border: 1px solid #333;
background: #EEE;
position: relative;
top: 0;
}

#box.moveBox {
left:0;
-webkit-animation-name: moveBoxAnimation; /*@keyframesで定義したアニメーション名*/
-webkit-animation-duration: 1s; /*アニメーションにかかる時間*/
-webkit-animation-iteration-count: 1; /*ループ回数*/
}

@-webkit-keyframes moveBoxAnimation { /*キーフレームルールの定義*/
0%{ /*始点*/
left: 0;
}
50% { /*中間点 複数設定可能*/
left: 1000px;
}
100% { /*終点*/
left: 0;
}
}
$1 pixel|サイバーエージェント公式クリエイターズブログ-UR_jsdo_h9Fw
@keyframesでアニメーション名と細かい動きの定義ができ、それを別で記載しておきます。
そのうえで適用したい要素に定義したアニメーション名と、ループの定義をします。
@keyframesの定義によってはかなり複雑な動きも再現できそうです。

transitions

特徴としては細かな動きの設定が難しい点と、ループさせられない点。

CSS Transitions - jsdo.it - http://jsdo.it/perma/x0MP
#box { 
width: 100px;
height: 100px;
border: 1px solid #333;
background: #EEE;
position: relative;
top: 0;
left:0;
-webkit-transition-property: left;
-webkit-transition-duration: 1s;
-webkit-transition-timing-function: linear;
}

#box.moveBox {
left:1000px;
}
1 pixel|サイバーエージェント公式クリエイターズブログ-UR_jsdo_x0MP
アニメーションさせたい要素に、アニメーションさせたい値とアニメーションの定義をします。
該当の要素の該当の値が変化すると設定した定義に従ってアニメーションします。
ループは出来ませんが、記述が簡単で画面のUIのアニメーションに向いていそうです。


実際の改修例 サブメニューの改修

ULTIMATE RACERではゲームという特性上ユーザーのアクションで一回だけ変化する要素が多く、その部分では主にtransitionsを使用しました。
animationは@keyframes設定が煩雑であったのとその管理コストかかりそうだったためです。
(ただし点滅する文字、アイコンといった永続的なループ処理が必要なものにはanimationを採用しています)

$1 pixel|サイバーエージェント公式クリエイターズブログ
※この点滅する文字のアイコンにはanimationを使用しています

以下に、画面の横から出てくるサブメニューを、jQueryのanimateからcssのtransitionsに書き換えた例をご紹介します。

1 pixel|サイバーエージェント公式クリエイターズブログ

jQueryのanimateの場合

/* メニューを表示する jQueryのanimateの場合*/
var subM = $("#sub");
var Main = $("#main");
var bothM = $("#sub,#main");

$(document).on("click",".showSub", function() {//表示ボタン押下したら

subM.css({"visibility":"visible","opacity":"1"});//非表示解除

subM.animate( //アニメーションしながら
{left : "60px"},//サブメニューの移動位置(=メインコンテンツが残るスペース分)
500);
Main.animate({
right : "260px" //メインコンテンツの移動距離
}, 500);


Main.css({"min-height" : "1000px"}); //サブメニュー以上の高さ指定
Main.prepend("<div id='mask'></div>"); //メインコンテンツ上を押下したら戻るマスク用意
});

/* メニューを非表示する jQueryのanimateの場合*/
$(document).on("click","#mask,#sub header", function() {//マスク、サブメニュー上部押下したら

subM.animate(
{left : "320px","opacity":"0"},//もとの位置へ
{
duration: 500,
complete: function(){subM.css({"visibility":"hidden"});}//アニメーション完了後に非表示
}
);
Main.animate({
right : "0"//もとの位置へ
}, 500);


  Main.css({"min-height" : "0"});//高さ指定解除
$("div").remove("#mask");//マスク解除
});

cssのtransitionsの場合

/* メニューを表示する cssのtransitionsの場合*/
var subM = $("#sub");
var Main = $("#main");
var bothM = $("#sub,#main");

$(document).on("click",".showSub", function() {//表示ボタン押下したら

subM.css({"visibility":"visible","opacity":"1"});//非表示解除+透過アニメーション
bothM.css({
'-webkit-transform' : 'translate(-260px,0)',//移動距離、アニメーション設定はCSS
});


Main.css({"min-height" : "1000px"});//サブメニュー以上の高さ指定
Main.prepend("<div id='mask'></div>"); //メインコンテンツ上を押下したら戻るマスク用意
});

/* メニューを非表示する */
$(document).on("click","#mask,#sub header", function() {//マスク、サブメニュー上部押下したら

subM.css({"opacity":"0"});//透過しながら
bothM.css({
'-webkit-transform' : 'translate(0,0)',//もとの位置へ、アニメーション設定はCSS
});


subM.on('webkitTransitionEnd', function () {//アニメーション完了後イベント設定
subM.css({"visibility" : "hidden"});//アニメーション完了後に非表示
subM.off('webkitTransitionEnd');//アニメーション完了後イベント設定解除
});


Main.css({"min-height" : "0"});//高さ指定解除
$("div").remove("#mask");//マスク解除
});

■cssにアニメーション設定
#sub,
#main {
-webkit-transition:-webkit-transform 500ms linear,opacity 500ms linear;
}

それぞれ赤くハイライトしたanimateメソッドの部分と、transformを設定しているcssメソッドがそのまま差し変わっています。この改修にhtmlの変更は行っていませんし、cssも動かす要素にアニメーションの設定を一行追加しただけです。

※ただし注意していただきたいのが黄色くハイライトした部分でアニメーション終了のタイミングでvisibilityを切り替える処理の部分も変わっている点です。
jQueryはanimateの引数のcompleteでタイミングを取って処理していますが、transformの場合はwebkitTransitionEndをイベントリスナーとして要素に登録して処理しています。
この際、一回毎に要素からwebkitTransitionEndの登録を削除しないと、同じ処理を繰り返した場合に意図しない不具合が発生することがあります。ご注意ください。


以下に前後の動きの動画をご用意しました。

サブメニュー jQueryのanimateの場合



サブメニュー cssのtransitionsの場合



かなり動きが滑らかになっているのが分かるかと思います。
こういった改修を全体に施し、ユーザーに快適にプレイをしていただけるように努力を続けています。


最後に

最後までお付き合いいただきまして、本当にありがとうございます。

動画がiPhoneでのデモンストレーションのみで、多くの方が「Androidはどうなの?」と思われているかと思います。
正直Androidは機種間の差異が激しくjQueryのanimateはもちろん、cssアニメーションでもぎこちなくなる端末があります。
ただしjQueryによるアニメーションよりは大きく改善することは明らかです。
実際にその動きを確認されたい方というは、ぜひぜひULTIMATE RACERを遊んでみて確かめていただければと思います!

今回は開発時の苦労話のご紹介ということで、多少お恥ずかしい部分も含めてご紹介させていただきました。
スマートフォンが対象でありながら実機での画面確認を後に回してしまったことも、jQueryのanimateが重いことを知っていながら開発を急いで安易に選択してしまったことも非常に反省しています。
スマートフォンでサービスを制作する際、PCとはまったく違う感覚で、よりユーザーの立場を考慮した制作を心掛けなければならないと再認識させられました。

今後とも、より良いサービスを目指して改善を続けていきますので、何卒よろしくお願いします。
まだまだ技術的にも未熟で足らない部分が多々あるかと思います。
今回の内容についても不備や誤認識等ありましたら、ご指摘いただければ幸いです。

最後に、改めてではありますが、何卒ULTIMATE RACERをよろしくお願いします!

ULTIMATE RACER
$1 pixel|サイバーエージェント公式クリエイターズブログ-UR_URL
※スマートフォン専用サービスとなっています。スマートフォンでアクセスしてください。