投稿日: 2012年05月17日(2013年1月20日更新)

今回は、keyframesを使わずにCSS3 Transitionを順番に適用してアニメーションさせるお話です。

スマートフォン向けのWebサイトで動きのある演出をしたい場合、多くの選択肢が考えられます。
FLASH、CANVAS、SVG、CSS3等々。しかしiPhone、Androidというプラットフォームで両立させるには選択肢は限られてきます。
WallabyやSwiffyのようなFLASH変換ツールもありますが、これらのパフォーマンスはあまり良くありません。
悩ましい問題です。

今回の目標は下記です。

  1. スマートフォンブラウザでスムーズに動作する
  2. 複数のモーションを順番に再生する
  3. できるだけ簡潔に

そして、下記のアプローチで実装してみました。

  1. CSS3 transform
  2. キュー
  3. メソッドチェーン

iPhoneとAndroidの主要なデバイスで動作させるために試行錯誤した結果、Androidで悲惨な目にあったので、その解決法もTipsとして含めてご紹介します。

基本、箇条書きです。サンプルコードはjQueryベースです。

目次

実装例
# 1つのモーションを適用する
# 複数のモーションを順番に適用する
プラグイン化
# Transitionを順番に適用するjQueryプラグイン
Tips
# transformで画面がチラつく場合、3dプロパティを使ってみる
# transformに複数のプロパティ値を適用する際は順番に注意
# Androidではskewがまともに動作しない
CSS3のメリット確認
# 要素の移動アニメーションを比較
# 要素の拡大・縮小アニメーションを比較
要素に1つのモーションを適用させるのはごく簡単です。duration(変化にかかる時間)プロパティと共にスタイルを設定するだけです。
このセクションではjQueryを使って簡単なモーションを適用してみます。
実際のコードは下のCodeタブで確認できます。
※このデモはwebkitのみ対応
説明: jQueryのcssメソッドでstyle属性を書き換える。
$('#btn-demo1').bind('click', function(){ 
  setTransition( 500, 0, 'translate( 150px, 0px )', $('#cssjs-demo') );
});
function setTransition( duration, delay, transform, selector ){
  return selector.css({
    '-webkit-transition-duration' : duration + 'ms',
    '-webkit-transition-delay' : delay + 'ms',
    '-webkit-transform' : transform,
  });
}

このようにstyle属性を書きかえることで、任意のタイミングでアニメーションを変化させることができます。

▲目次

いわゆるキューを操作して、複数のモーションを、登録したものから1つずつ順番に再生する仕組みを実装してみます。
jQueryのqueueメソッドを利用することで、わりと簡単に実装することができます。
イベントリスナでtransitionendイベント(Transitionの終了時に発行される)を捕捉し>て、スタイルを更新するパターンも考えられますが、このイベントはOSのバージョンによって稀に発行されないことがあるらしいので、今回は使用していません。

※デモはwebkitのみ対応
説明:
queueメソッドの引数にモーション設定用の関数を指定して、メソッドチェーンで繋げています。
2つ目以降のモーションは、直前のモーションの再生時間分待機させたいのでdelayメソッドで遅延させています。
$('#btn-demo2').bind('click', function(){ 
  $('#queue-demo')
  .queue(function(){
    setTransition( 500, 0, 'translate( 150px, 0px ) scale( 1, 1 ) rotate( 0deg )', $(this) );
    $(this).dequeue();
  }).delay(500)
  .queue(function(){
    setTransition( 500, 0, 'translate( 150px, 0px ) scale( 2, 2 ) rotate( 0deg )', $(this) );
    $(this).dequeue();
  }).delay(500)
  .queue(function(){
    setTransition( 500, 0, 'translate( 100px, 0px ) scale( 1.5, 1.5 ) rotate( 180deg )', $(this) );
    $(this).dequeue();
  });
});

function setTransition( duration, delay, transform, selector ){
  return selector.css({
    '-webkit-transition-duration' : duration + 'ms',
    '-webkit-transition-delay' : delay + 'ms',
    '-webkit-transform' : transform,
  });
}
▲目次

CSS3 Transitionsはプラットフォームによって所々挙動が異なります。不具合のようなパターンも存在します。(この次のTipsセクションで紹介します。)
それらをプラットフォーム別に1つ1つ対応するのは非常に面倒な話です。
ですので開発の初期段階で、CSSの適用部分を共通化しておくことが、後々後悔しない術だと思います。

この記事を書く傍らで、Tipsセクションの不都合を吸収するjQueryプラグインを作ってみました。
下のボタンからソースをダウンロードできます。

Download

GitHub

プラグインの処理内容
犬が攻撃して相手のゲージを変動させるゲーム風のデモ
var demo1Start = function(){ 
  var maru1Attack = function(){
    $("#maru1")
      .enqt( { translate:{ x:'200px', y:'0px' }, scale:{ x: 1.5, y: 1.5 }, rotate: '45deg' }, 500, maru2Damage )
      .enqt( { translate:{ x:'0px' , y:'0px' } }, 500 );
  }
  var maru2Damage = function(){
    $("#gage2")
      .enqt( { width: Math.random() * 20 +'px', backgroundColor: '#f00' }, 500 );
    $("#maru2")
      .enqt( { opacity:0.3, scale:{ y: 0.5 } }, 500 )
      .enqt( { opacity:1.0 }, 500, maru2Attack );
  }
  var maru2Attack = function(){
    $("#maru2")
      .enqt( { translate:{ x:'-200px', y:'0px' }, scale:{ x: 1.5, y: 1.5 }, rotate: '-45deg' }, 500, maru1Damage )
      .enqt( { translate:{ x:'0px' , y:'0px' } }, 500 );
  }
  var maru1Damage = function(){
    $("#gage1")
      .enqt( { width: Math.random() * 20 +'px', backgroundColor: '#f00' }, 500 );
    $("#maru1")
      .enqt( { opacity:0.3, scale:{ y: 0.5 } }, 500 )
      .enqt( { opacity:1.0 }, 500, function(){ $('#btn-attack1').one('click', demo1Start ) } );
  }
  maru1Attack();
};
$('#btn-attack1').one('click', demo1Start );

Android2系でのscaleは荒ぶってます。
良い実装あれば教えて下さい。

次のTipsセクションからは、プラットフォーム別の(Androidの)挙動の紹介です。

▲目次

transform translate

こちらがチラつくデバイスは、下のPlayを試す。

transform translate3d

※PCブラウザでは同じアニメーションにしか見えません。

Memo
Galaxy Nexus(Android4)では3dにすることでチラつきが改善した。
しかしAndroid2系でtranslate3dを使うとアニメーション後にscaleやrotateの状態が保持されない様子。
プラグインではバージョンによって3dの有無を切り替えるようにした。 iPhoneは両方スムーズ。

▲目次
transform:
translate(200px, 0px) scale(2, 2)
transform:
scale(2, 2) translate(200px, 0px)

Memo
transformに複数の変形処理を指定する場合、同じ値だとしても順番が異なるとモーションも異なる。
Android2系では、値の順番が異なる場合や、前後のアニメーションと値の数に差異がある場合、Transitionを切り替える際に変形したオブジェクトが初期状態に戻ってしまうことがある。
プラグインでは、指定されていないtransformメソッドを初期値かつ同じ順番で追加するようにした。

▲目次
※このデモはwebkitのみ対応

transition

skew( 60deg )

keyframes

skew( 60deg )

Memo
Android2系ではアニメーションしてくれません。Galaxy Nexus(Android4)は動きました。解決策あれば教えて下さい...。

▲目次

left:

left: 0px → 200px → 0px

transform:

translateX: 0px → 200px → 0px

Memo
スマートフォンで見るとtransformXの方がスムーズに動いている。
位置はpxだけでなく、%指定も可。

▲目次

width:

width: 50px → 200px → 50px

transform scale:

scaleX: 1 → 4 → 1

Memo
PCで見ると分かりにくいが、iPhoneで見ると明らかにtransform scaleの方がスムーズに動く。
iPhoneシミュレーターのブレンドレイヤーで確認すると、transformの要素のみGPUアクセラレーターが効いていることがわかる。
ただしAndroid2系ではscaleの動作が怪しいので、widthが無難かもしれません。

▲目次



作成者: @7vsy

ホームへ戻る