Webページのアニメーション
概要
Webのモーションは、2つの方法で失敗します。動作しないか、動作が悪いかです。どちらにも、特定可能な原因があります。イージングカーブを推測することは、最初に行うことではなく、最後に行うことです。
基本原則:アニメーションを調整する前に、それが意図した要素で実行されていることを証明すること。
固定された、スクラブされるシーンのデザインについては、choreographing-scroll-storiesを参照してください。このスキルは、モーションを実行させ、適切に動作させるためのメカニズムです。
アニメーションがまったく動作しない場合
以下の順序で確認してください。最初の3つでほとんどすべての原因が明らかになります。
| 症状 | 原因 | 確認方法 |
|---|---|---|
| 何も動かない、要素は表示されている | セレクタが一致する要素がゼロ | コンソールで document.querySelectorAll(sel).length |
| 何も動かない、要素は非表示 | リビールに opacity: 0 が設定され、トリガーが発火しなかった | 要素の computed opacity |
| クロスフェードが機能しない | 両方のレイヤーが opacity: 1 で、"on" のルールのみ存在する | 入ってくるレイヤーを 0 に設定する休止状態のルールが存在する必要がある |
| 読み込み直後に発火する | トリガーがレイアウト確定前に評価された | 画像がページのサイズを決定する load の後に実行する。DOMContentLoaded ではない |
| 一度発火した後に二度と発火しない | once: true と、再エントリーする要素 | 意図的か? |
| 非表示タブやヘッドレスパネルで動作しない | document.hidden の間、ティッカーが一時停止している | 強制する:gsap.ticker.lagSmoothing(0); gsap.ticker.tick() |
| トリガーが数百ピクセルずれている | トリガーが測定された後に画像が読み込まれた | 読み込み時とリサイズ時に ScrollTrigger.refresh()(200msのデバウンス) |
クロスフェードの症状は、コードレビューでは正しく見えるため、詳しく説明する価値があります:
/* これだけでは何も起こらない。 */
.tile.is-swapped .layer.is-alt { opacity: 1 }
/* 休止状態がアニメーションを成立させる。 */
.layer.is-alt { opacity: 0 }
.tile.is-swapped .layer.is-alt { opacity: 1 }
イントロとアンビエントモーション
- 見出しはすぐに表示されること。 イントロは、見出しが約1.1秒で表示されるようにカットすること。3.8秒のイントロはローディング画面です。
- 写真は、1つのセルから別のセルにフェードアウトして変化すること。スライドしないこと。 各写真は1つの要素であり、画面に2度表示されることはなく、そのコピーがフェードインしている間にフェードアウトすることもありません。
- アンビエントの変化は遅く、不規則であること。 1.8秒から3.4秒ごとに1つのタイルが変化する場合、各タイルの変化は遅く、生きているグリッドとして読み取られます。各タイルをより速く変化させるために間隔を短くしないでください。
- ラウンドごとにシャッフルすること。 すべてのタイルが繰り返される前に一度ずつ変化し、各選択は最後の3つのうちの遠い半分から行われます。
- 誰も見えないものは一時停止すること。 アンビエントループは画面外と非表示タブで停止し、中断した場所から再開します。
- 戻るたびにイントロが再生されないこと。 イントロが安定した後は、アンビエントレイヤー以外は動きません。
スクロールリビール
- 読み込み時に実際にフォールドの下にある要素にのみ
opacity: 0を設定すること。すべてを全面的に非表示にすると、フォールドより上の要素は、発生しないかもしれないスクロールまで見えなくなります。 - 完全な交差ではなく、
top 88%でリビールすること。要素が完全にビュー内に入るのを待つと、読者がすでにそれを見ているときにアニメーションが実行されます。 - コンポーネント内ではなく、兄弟間でスタガーを設定すること。カード間は60-90ms、カード内は0。
- 要素ごとに1つではなく、グループごとに1つのオブザーバーでバッチ処理すること。
動画
自動再生の背景動画には、普遍的なバグが1つあります。動画が画面外で再生され、オブザーバーがそれを一時停止し、読者が到着する頃にはループが15秒進んでいて、冒頭のビートが失われています。
autoplay属性を完全に削除します。
初期化時に pause() と load() を実行します。
最初の交差時のみ:currentTime = 0、次に play() を実行します。
992px未満またはデバイスピクセル比1.5未満では1xファイルを提供し、動画がデコードされる前のペイント用にポスターフレームを提供します。負荷の高いホームページ動画は帯域幅を消費する場所でもあるため、監査してください。
スタッター(吃音)
静止写真でのゆっくりとしたドリフトが最も一般的な原因です。1フレームあたり約1ピクセル未満のケンバーンズ効果は、フレームごとに画像をリサンプリングし、揺らぎとして認識され、モーションとしては認識されません。
- モーションとして認識できるほど十分に動かすか、まったく動かさないかのどちらかです。
- レンダリングされた動画内の静止フレームについては、同一フレームを再エンコードするのではなく、エンコードされたフレームをバイト単位でコピーします。それ以外の方法ではドリフトが発生します。
- ループの継ぎ目を確認します。フレーム0と最終フレームが異なる場合、ループはサイクルごとに目に見えてジャンプします。それらの間の平均絶対差を測定します。
- ダブルスムージングを行わないでください。スムーススクロールライブラリと重い
scrub値が組み合わさると、ホイールに遅れるページが発生します。スムーススクロールライブラリが存在する場合は、scrub: 0.2を使用します。
スティッキー
CSSグリッド内の position: sticky は、コンテナ全体ではなく、グリッド領域 に対して解決されます。自身のコンテンツの高さしかないスティッキーアイテムは移動スペースがなく、すぐに解放されます。
.grid { align-items: stretch } /* 領域がフルハイトになる */
.col { position: static; height: 100% }
.inner { position: sticky; top: 10rem }
フォールバック
すべてのアニメーションには、モーション仕様書に記載され、テストされた3つのフォールバックがあります。
- 動きの抑制(Reduced motion)。
prefers-reduced-motion: reduceと?staticクエリは、積み重ねられた、完了した状態をレンダリングします。固定されたものはなく、非表示のものもなく、すべてのシーンは最終状態にあります。追加したすべてのCSStransitionもカバーします。アニメーション化されたセレクタをリストしても、コンテナのトランジションを忘れると、色がまだスライドしている状態になります。 - スクリプトなし(No script)。 すべての要素が表示されたままになります。スクリプトが開始されなかった場合、約3.5秒後にクラスが追加され、CSSはその下のすべてをリビールします。
- 携帯電話(Phones)。 固定されたシーンは積み重なります。それを明記し、テストします。
検証
コードからアニメーションが動作していると報告しないでください。ページ上で確認します:
- アニメーション化されたすべてのグループのセレクタ数がゼロ以外である
- フォールドより上の要素の computed
opacityが、スクロール前に1である - 動画の
currentTimeが、最初に表示された瞬間に0.5秒未満である - 動きの抑制エミュレーションにより、ページが完全に読み取り可能で静的になる
- スクリプトなしですべてが表示されたままになる
-
width、height、top、leftをアニメーション化しない。transformとopacityを使用する - アンビエントループは、画面外ではスタイル変更を行わない
危険信号(Red flags)
- セレクタの一致を確認する前に、期間やイージングを調整する
- 対応する休止状態のルールがない
.is-activeルール transition: all- セクションが「空っぽに感じられた」ために追加されたアニメーション
- スライドする写真
- 同じ写真が画面に2度表示される