スキップしてメイン コンテンツに移動

ぺったんRフレームワークのアニメーションメソッド

アニメーション中の要素の、その子要素に加えた変更がリアル DOM ツリーに適用されない件に関する注意を追記しました。合わせてマークアップの修正もしました。(2021/05/01)

2016-01-08の更新から fallback の選択肢に right, bottom を追加しました。

2015-12-19の更新から rotate, skew, scale, scroll に対応し引数を変更しました。

概要

アニメーションのための低レベルなメソッドです。位置指定については px 指定しかできません。(%, em 等は使えません)

GPU サポートの効くプロパティ(transform, opacity)に絞って絹のように滑らかなアニメーションを実現します。

transform が無い環境用に複数のフォールバックを用意しています。

// アニメーション登録
xnode.animate({
    from : { x : 0, y : 100 },
    to   : { x : 100, y : 0 },
    duration    : 500,
    easing      : 'circular',
    lazyRelease : 250 });

// アニメーション停止
xnode.stop();

// 即時にアニメーションできるか?
var bool = xnode.call( 'canAnimateNow' );

// アニメーション状態 0~7
var num = xnode.call( 'animeState' );

// アニメーション進行状況 0~1
var num = xnode.call( 'animeProgress' );

// GPU レイヤーで描画されてる
var bool = xnode.call( 'inGPU' );

.animate()の引数

key説明値の型デフォルト値
from開始値を指定するObject 下表参照{}
to終了値を指定するObject 下表参照{}
durationアニメーションする時間Number ミリ秒0(ms)
lazyReleaseGPU レイヤーの解除を遅延する時間Number ミリ秒0(ms)
easingイージング関数'quadratic', 'circular', 'back', 'bounce', 'elastic' または関数'circular'
fallbackフォールバック動作の指示Number ビットフラグ 下表参照0

fromto の値

key説明値の型デフォルト値
xx 座標を指定px 指定の数値0(px)
yy 座標を指定px 指定の数値0(px)
rotate要素の回転、基準は要素の中心degree0(°)
skewX要素を歪める、基準は要素の中心degree0(°)
skewY要素を歪める、基準は要素の中心degree0(°)
skewskewY, skewY を同時に指定するdegree0(°)
scaleX要素を拡大する、基準は要素の中心0 以上の数値1
scaleY要素を拡大する、基準は要素の中心0 以上の数値1
scalescaleX, scaleY を同時に指定する0 以上の数値1
scrollX実装中
scrollY実装中

from のメンバーや from 自体が省略可能です。省略した場合、現在値またはデフォルト値が新たな開始値になります。

fallback の値

代替手段説明ビットフラグ
DX Transformrotate, skew, scale に対して DXImageTransform を使用32
bottomtransfrom のない環境で top に替えて bottom を使用16
righttransfrom のない環境で left に替えて right を使用8
zoomscale に対して IE 独自拡張の zoom プロパティを使用。scaleX scaleY が同じ値の場合に限る4
fontSizescale に対して fontSize プロパティを使用。フォントサイズベースで要素をレイアウトしていること。scaleX, scaleY が同じ値の場合に限る。2
width & height未実装。子要素がないか、あってもテキストが無く % でレイアウトを組んでいる場合に互換性がある。1

opecity のない IE8 以下に対して filter:alpha の使用、transfrom のない環境で left, top の使用は fallback に関わらず行われます。

制約

GPU サポートの効くプロパティとそうでないプロパティを混在させることはできないため、アニメーションできるプロパティをバッサリと絞っています。

そのうえ、GPU サポートの制約によって先祖・子孫関係にある要素を同時にアニメーションできません

このため、先行してアニメーション中の先祖(親)要素または子孫要素があった場合に要素はアニメーションを待機します。

待機の例

要素 B は、先祖(親)要素 A や 子孫要素 C~F がアニメーション中の場合、それらが終わってからアニメーションを開始します。

(A)-+-(B)-+-(C)
    |     |
    |     +-(D)
    +-(G) |
            +-(E)---(F)

よって待機時間が極端に長くなってしまう1秒を超える長時間のアニメーションを行うには不向きです。

先祖子孫制約を回避する

制約を見越して要素のレイヤー関係を設計します。無用な親子関係を作らずルート要素に対してフラットに並べることができないか、検討します。

(Root)-+-(A)
       |
       +-(B)
       |
       +-(C)

animeState:アニメーション状態変化

  1. (アニメーション未登録)
  2. 登録直後
  3. 待機
    自身のアニメーション開始を、待機または後続待機中の先祖子孫要素によって阻害されている
  4. 後続待機
    自身のアニメーション開始をアニメーション中の先祖子孫要素によって阻害されている
  5. 強制停止(GPU転送予約)
  6. GPU解除待ち
  7. アニメーション開始可能
  8. アニメーション中

duration が 0 でも lazyRelease を指定しておけば要素は状態4に進みます。

duration, lazyRelease がどちらも 0 の場合、transform, opacity の設定のみ行うことができます。状態は 4 → 0 に変化します。但し親要素がアニメーション中の場合、変更の反映は親のアニメーション終了を待ちます。

イベント

  1. X.Event.ANIME_START
  2. X.Event.ANIME_END
  3. X.Event.GPU_RELEASED

動作

待機とアニメーションの開始

先祖・子孫関係にある複数の要素がアニメーション登録された場合、次のルールに従ってアニメーション開始・待機します。

  1. アニメーションを開始する優先権は先に登録されたものにある
  2. 登録されたばかりの要素は自分より登録が先で待機(& 後続待機)中の要素の中に親子関係のものがいないか検査される
    • いる -> 待機フラグを立てる、この段階では誰に後続してアニメーションするのか、判然としない
    • いない -> 3. へ
  3. アニメーション中の要素(開始可能含む)の中に親子関係のものがいないか?調べる
    • いる -> アニメーション中の要素に後続する要素がいることをチェックし(following)後続待機フラグを立てる
    • いない -> 4. へ
  4. アニメーションが開始可能

以上の検査はアニメーション登録後から一番最初のアニメーションの際に行われる。その結果には animeState でアクセスできます。

しかし、即座にアニメーションを開始できるか?を判別して処理を切り替えたい場合に不都合です。

即時に開始できるか調べる

var bool = xnode.call( 'canAnimateNow' );

アニメーションを開始する前に、要素がただちにアニメーション可能か?確認することができます。

後述する理由で、子要素以下の変更が出来ない為に、アニメーション中の親要素に追加された子要素は、親のアニメーション終了までリアル DOM ツリーへの変更適用(commit)が行われず、要素のサイズ取得(xnode.width())で0が返ります。アニメーションする可能性のある要素の子要素の、xnodeChild.call( 'canAnimateNow' )true ならば、直ちにサイズの取得が出来ます。(2021/05/01 に追記)

待機と後続待機がある理由

(A)-+-(B)-+-(C)
    |     |
    |     +-(D) *
    +-(E)

要素 D のアニメーション中に B が、続いて C がアニメーション登録されたとする。(D ← B ← C )

B は子要素 D のアニメーション終了を待機するが C にはアニメーション中の先祖子孫要素がいないため直ちにアニメーション開始可能である。

仮に C がアニメーションを開始すると、B は子要素 C の終了を待たなくてはならなくなる。

この結果アニメーションの順番が登録順と異なってしまい具合が悪いです。(D ← C ← B )

これを防ぐために、D の終了を待つ B は後続待機となり、後続待機(& 待機)を親に持つ C は待機になる。

ちなみに B, C に続いて E のアニメーションが登録された場合、B C D と親子関係にない E のアニメーションは即座に開始される。

translateX translateY だけをセットしてアニメーションはしない

xnode.animate( { from : { x : 100, y : 100 } } );

アニメーションをすぐには開始しないが表示位置や opacity は変更しておきたいケースがあります。

このような場合には開始値だけを与えて .animate() します。

一見すると .css() を単に .animate() で代替にしているようにみえます。

実は浅からぬ意味があり .animate().css()translateXleft がバッティングしないために translateX translateY の変更には .animate() を使用すべきです。

先祖要素が GPU 化していない場合、変更は直ちに反映されます。

GPU レイヤーの遅延解除

要素を GPU メモリに転送することで GPU による高速な描画が可能になります。

この転送にはコスト(時間)がかかります。対象要素が大きい場合などに画面がチラツキます。

iPod touch 1th~2th は GPU 描画をサポートする最も非力な端末で特にチラツキが顕著です。(もちろん最新の端末でも頻度は落ちますが発生します)

このような端末でもレイヤーの遅延解除を活用することでチラツキを抑えることが出来ます。

アニメーション終了後、即座に GPU レイヤーの解除を行わない

スクロールのようなケースでは、サッサッと矢継ぎ早にアニメーションが発生します。

そのたびに GPU の解除と再転送を行うのは無駄な上にチラツキが頻発し UX を損ないます。

アニメーションの終了から GPU の解除までに若干のタイマーを挟むと、まだ GPU レイヤーにあるうちに次のアニメーションが起こるためチラツキが発生しません。

しかし、要素が GPU レイヤーにあるうちは transform, opacity の以外の値・属性・子要素以下の変更は出来ません。より正確にはリアル DOM ツリーに変更が適用(commit)されません。ですので、いつまでも GPU レイヤーに要素を留めておくことは、現実的ではありません。

GPU レイヤーの強制解除

GPU レイヤーの遅延解除中の要素が他の要素のアニメーション開始を阻害している場合、GPU レイヤーは解除されます。

アニメーション発生前に GPU 転送だけを行う

// GPU レイヤー化だけを行う 0.5秒後に解除
xnode.animate( { lazyRelease : 500 } );

lazyRelease だけを設定して xnode.animate() する。

アニメーションの中断

登録直後からアニメーション中のどの段階でも、xnode.stop(opt_bitFlag) でアニメーションの中断が出来ます。引数に 1~3 の数字を指定して座標と透過度と GPU レイヤーの状態を制御できます。

  1. RESET
  2. KEEP_GPU
  3. RESET | KEEP_GPU
状態 (animeState)引数動作
待機、開始前、強制停止 (~4,6)-アニメーションをキャンセル、開始値を保持、GPU は解除
RESETアニメーションをキャンセル, x=0,y=0,opacity=1, GPU は解除
KEEP_GPU開始値を保持してアニメーションをキャンセル GPU はキープ
RESET | KEEP_GPUアニメーションをキャンセル, x=0,y=0,opacity=1, GPU はキープ
アニメ中 (7)-アニメーションを停止、現在値を保持、GPU は解除
RESETx=0,y=0,opacity=1 アニメーションを停止, GPU は解除
KEEP_GPUアニメーションを停止、現在値を保持、GPU はキープ
RESET | KEEP_GPUアニメーションを停止, x=0,y=0,opacity=1, GPU はキープ
GPU 解除遅延中 (5)-GPU レイヤーを即時に解除
RESETx=0,y=0,opacity=1, GPU は解除
KEEP_GPU何もしない
RESET | KEEP_GPUx=0,y=0,opacity=1 にする

要素の座標

animate() の移動量は xnode.x(), xnode.y() に反映されません。

translateX translateYelm.offsetLeft, elm.offsetTop に反映されないためです。(今日知った、、、2015.12.15)

これでは CSS-P 代替モードで動作している場合だと値が一致しないですね、、、

謝辞

DXImageTransform によるフォールバックは次の記事からコードを取り込ませていただきました。御礼申し上げます。

ちなみに rti7743 氏の次のコメントの問題には遭遇しませんでした。

//skewの補正(rotate しながらskew すると補正がおかしくなります。 これがわからない)

メモ書き

willChange?

transform のサポートレベルはまちまちで、最も動作する px指定に限定

CSS transitions は使わない。transitionend イベントが起こらない環境があるっぽい、、、コードが複雑になるわりに、最後まで動作が安定しなかった、、、