ぺったん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) |
lazyRelease | GPU レイヤーの解除を遅延する時間 | Number ミリ秒 | 0(ms) |
easing | イージング関数 | 'quadratic', 'circular', 'back', 'bounce', 'elastic' または関数 | 'circular'
|
fallback | フォールバック動作の指示 | Number ビットフラグ 下表参照 | 0 |
from と to の値
| key | 説明 | 値の型 | デフォルト値 |
|---|---|---|---|
x | x 座標を指定 | px 指定の数値 | 0(px) |
y | y 座標を指定 | px 指定の数値 | 0(px) |
rotate | 要素の回転、基準は要素の中心 | degree | 0(°) |
skewX | 要素を歪める、基準は要素の中心 | degree | 0(°) |
skewY | 要素を歪める、基準は要素の中心 | degree | 0(°) |
skew | skewY, skewY を同時に指定する | degree | 0(°) |
scaleX | 要素を拡大する、基準は要素の中心 | 0 以上の数値 | 1 |
scaleY | 要素を拡大する、基準は要素の中心 | 0 以上の数値 | 1 |
scale | scaleX, scaleY を同時に指定する | 0 以上の数値 | 1 |
scrollX | 実装中 | ||
scrollY | 実装中 |
from のメンバーや from 自体が省略可能です。省略した場合、現在値またはデフォルト値が新たな開始値になります。
fallback の値
| 代替手段 | 説明 | ビットフラグ |
|---|---|---|
| DX Transform | rotate, skew, scale に対して DXImageTransform を使用 | 32 |
bottom | transfrom のない環境で top に替えて bottom を使用 | 16 |
right | transfrom のない環境で left に替えて right を使用 | 8 |
zoom | scale に対して IE 独自拡張の zoom プロパティを使用。scaleX scaleY が同じ値の場合に限る | 4 |
fontSize | scale に対して 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:アニメーション状態変化
- (アニメーション未登録)
- 登録直後
- 待機
自身のアニメーション開始を、待機または後続待機中の先祖子孫要素によって阻害されている - 後続待機
自身のアニメーション開始をアニメーション中の先祖子孫要素によって阻害されている - 強制停止(GPU転送予約)
- GPU解除待ち
- アニメーション開始可能
- アニメーション中
duration が 0 でも lazyRelease を指定しておけば要素は状態4に進みます。
duration, lazyRelease がどちらも 0 の場合、transform, opacity の設定のみ行うことができます。状態は 4 → 0 に変化します。但し親要素がアニメーション中の場合、変更の反映は親のアニメーション終了を待ちます。
イベント
X.Event.ANIME_STARTX.Event.ANIME_ENDX.Event.GPU_RELEASED
動作
待機とアニメーションの開始
先祖・子孫関係にある複数の要素がアニメーション登録された場合、次のルールに従ってアニメーション開始・待機します。
- アニメーションを開始する優先権は先に登録されたものにある
- 登録されたばかりの要素は自分より登録が先で待機(& 後続待機)中の要素の中に親子関係のものがいないか検査される
- いる -> 待機フラグを立てる、この段階では誰に後続してアニメーションするのか、判然としない
- いない -> 3. へ
- アニメーション中の要素(開始可能含む)の中に親子関係のものがいないか?調べる
- いる -> アニメーション中の要素に後続する要素がいることをチェックし(following)後続待機フラグを立てる
- いない -> 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() や translateX と left がバッティングしないために 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 レイヤーの状態を制御できます。
RESETKEEP_GPURESET | 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 は解除 |
RESET | x=0,y=0,opacity=1 アニメーションを停止, GPU は解除 | |
KEEP_GPU | アニメーションを停止、現在値を保持、GPU はキープ | |
RESET | KEEP_GPU | アニメーションを停止, x=0,y=0,opacity=1, GPU はキープ
| |
| GPU 解除遅延中 (5) | - | GPU レイヤーを即時に解除 |
RESET | x=0,y=0,opacity=1, GPU は解除
| |
KEEP_GPU | 何もしない | |
RESET | KEEP_GPU | x=0,y=0,opacity=1 にする
|
要素の座標
animate() の移動量は xnode.x(), xnode.y() に反映されません。
translateX translateY が elm.offsetLeft, elm.offsetTop に反映されないためです。(今日知った、、、2015.12.15)
これでは CSS-P 代替モードで動作している場合だと値が一致しないですね、、、
謝辞
DXImageTransform によるフォールバックは次の記事からコードを取り込ませていただきました。御礼申し上げます。
ちなみに rti7743 氏の次のコメントの問題には遭遇しませんでした。
//skewの補正(rotate しながらskew すると補正がおかしくなります。 これがわからない)
メモ書き
willChange?
transform のサポートレベルはまちまちで、最も動作する px指定に限定
CSS transitions は使わない。transitionend イベントが起こらない環境があるっぽい、、、コードが複雑になるわりに、最後まで動作が安定しなかった、、、