好まれも悪くも、パララックスは定着しつつあります。適切に使用すると、ウェブアプリに奥行きと複雑さが加わります。ただし、パララックスを効率的に実装するのが難しいという問題があります。この記事では、パフォーマンスに優れ、クロスブラウザで動作するソリューションについて説明します。
要約
- 視差アニメーションの作成にスクロール イベントや
background-position
を使用しないでください。 - CSS の 3D 変換を使用して、より正確な視差効果を作成します。
- Mobile Safari の場合は、
position: sticky
を使用して視差効果が伝播されるようにします。
ドロップイン ソリューションが必要な場合は、UI 要素のサンプルの GitHub リポジトリにアクセスし、パララックス ヘルパー JS を入手してください。GitHub リポジトリで、パララックス スクロールのライブデモを確認できます。
パララックスの問題
まず 視差効果を生み出す一般的な 2 つの方法 特に それらが目的に適さない理由を見ていきましょう
不適切: スクロール イベントを使用する
パララックスの主な要件は、スクロールに連動することです。ページのスクロール位置が変化するたびに、パララックス要素の位置が更新される必要があります。シンプルに聞こえますが、最新のブラウザの重要なメカニズムは、非同期で動作する機能です。この場合、スクロール イベントに適用されます。ほとんどのブラウザでは、スクロール イベントは「ベスト エフォート」として配信され、スクロール アニメーションのすべてのフレームで配信される保証はありません。
この重要な情報は、スクロール イベントに基づいて要素を移動する JavaScript ベースのソリューションを避ける必要がある理由を示しています。JavaScript では、ページのスクロール位置が視差と同じになるとは限りません。以前のバージョンのモバイル Safari では、スクロール イベントは実際にスクロールの終了時に配信されていたため、JavaScript ベースのスクロール エフェクトを作成できませんでした。新しいバージョンでは、アニメーション中にスクロール イベントが配信されますが、Chrome と同様に「ベスト エフォート」ベースです。メインスレッドが他の処理でビジー状態の場合、スクロール イベントはすぐに配信されません。つまり、パララックス効果は失われます。
不正: background-position
を更新しています
フレームごとにペイントすることも避けるべきです。多くのソリューションでは、視差効果を確保するために background-position
を変更しようとします。これにより、ブラウザがスクロール時にページの影響を受ける部分を再描画することになり、アニメーションが大幅にジャンクになるほどのコストがかかる可能性があります。
パララックス モーションの約束を果たすには、加速プロパティとして適用でき(現在は、変換と不透明度に固執することを意味します)、スクロール イベントに依存しないものが望まれます。
3D の CSS
Scott Kellum と Keith Clark は、CSS 3D を使用してパララックス モーションを実現する分野で重要な仕事をしてきました。彼らが使用している手法は、次のとおりです。
overflow-y: scroll
(おそらくoverflow-x: hidden
も)でスクロールするように、親要素を設定します。- 同じ要素に、
perspective
値と、top left
または0 0
に設定されたperspective-origin
を適用します。 - その要素の子に対して、Z で変換を適用し、画面上のサイズに影響を与えることなく視差効果が得られるように拡大縮小します。
このアプローチの CSS は次のようになります。
.container {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
perspective: 1px;
perspective-origin: 0 0;
}
.parallax-child {
transform-origin: 0 0;
transform: translateZ(-2px) scale(3);
}
これは、次のような HTML スニペットがあると仮定しています。
<div class="container">
<div class="parallax-child"></div>
</div>
遠近感のスケールの調整
子要素を後方に押し出すと、遠近感の値に比例して小さくなります。スケールアップする必要がある量は、(遠近感 - 距離)÷ 遠近感の式で計算できます。パララックス要素はパララックス効果を適用しつつ、作成したサイズで表示することがほとんどであるため、そのままにせず、この方法で拡大する必要があります。
上記のコードの場合、パースペクティブは 1px、parallax-child
の Z 距離は -2px です。つまり、要素を 3 倍に拡大する必要があります。これは、コードに挿入された値 scale(3)
で確認できます。
translateZ
値が適用されていないコンテンツには、0 の値を代用できます。つまり、スケールは (perspective - 0)/ perspective となり、値は 1 になります。つまり、スケールアップもスケールダウンも行われていないことを意味します。とても便利です。
このアプローチの仕組み
この知識は後ほど使用するので、なぜこれが機能するのかを明確にすることが重要です。スクロールは実質的に変換であるため、高速化できます。ほとんどの場合、GPU を使用してレイヤを移動します。遠近感のない一般的なスクロールでは、スクロール要素とその子要素を比較すると、スクロールは 1:1 で実行されます。要素を 300px
下にスクロールすると、その子は同じ量(300px
)変換されます。
ただし、スクロール要素に視点値を適用すると、この処理が煩雑になり、スクロール変換の基盤となる行列が変化します。選択した perspective
値と translateZ
値によっては、300 px スクロールしても子要素が 150 px しか移動しない場合があります。要素の translateZ
値が 0 の場合、これまでと同様に 1:1 でスクロールされますが、視点原点から Z だけ離れた子は異なる速度でスクロールされます。結果として、パララックス モーションが発生します。非常に重要な点として、これはブラウザの内部スクロール メカニズムの一部として自動的に処理されます。つまり、scroll
イベントをリッスンしたり、background-position
を変更したりする必要はありません。
薬に包まれたハエ: モバイル サファリ
すべてのエフェクトには注意点があります。トランスフォームに関する重要な注意点の 1 つは、子要素への 3D エフェクトの保持です。視点を持つ要素と視差効果のある子との間の階層に要素がある場合、3D 視点は「平坦化」され、効果が失われます。
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
上記の HTML では、.parallax-container
が新しく、perspective
値が効果的にフラット化され、パララックス効果がなくなります。ほとんどの場合、解決策はとても簡単です。transform-style: preserve-3d
を要素に追加すると、ツリーのさらに上流に適用された 3D 効果(視点値など)が伝播されます。
.parallax-container {
transform-style: preserve-3d;
}
ただし、モバイル Safari の場合は、もう少し複雑です。コンテナ要素に overflow-y: scroll
を適用することは技術的には可能ですが、スクロール要素をフリングできなくなります。解決策は -webkit-overflow-scrolling: touch
を追加することですが、perspective
もフラット化されるため、パララックスは発生しません。
段階的な機能強化という観点から見ると、これはそれほど大きな問題ではないでしょう。すべての状況でパララックスを使用できない場合でも、アプリは機能しますが、回避策を見つけるとよいでしょう。
position: sticky
が解決策です。
スクロール中に要素がビューポートの上部または特定の親要素に「固定」できるようにする position: sticky
形式のヘルプもあります。仕様は、ほとんどの場合かなりの量ですが、役に立つ小さな宝石が含まれています。
一見、あまり意味がないように思えますが、この文の重要なポイントは、要素のスティッキネスがどのように正確に計算されるかについて言及している点です。「オフセットは、スクロール ボックスを持つ最も近い祖先を参照して計算されます」。つまり、スティッキー要素を移動する距離(別の要素またはビューポートに接続されているように表示するため)は、他の変換が適用される前に計算され、後に計算されるわけではありません。つまり、前のスクロールの例と非常によく似て、オフセットが 300 px で計算された場合、その 300 px のオフセット値を、スティッキー要素に適用する前に、パースペクティブ(または他の変換)を使用して操作できるようになりました。
パララックス要素に position: -webkit-sticky
を適用すると、-webkit-overflow-scrolling:
touch
のフラット化効果を効果的に「反転」できます。これにより、パララックス要素はスクロール ボックスを持つ最も近い祖先(この場合は .container
)を参照します。次に、以前と同様に、.parallax-container
は perspective
値を適用します。これにより、計算されたスクロール オフセットが変更され、視差効果が作成されます。
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
.container {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.parallax-container {
perspective: 1px;
}
.parallax-child {
position: -webkit-sticky;
top: 0px;
transform: translate(-2px) scale(3);
}
これにより、Mobile Safari でパララックス エフェクトが復元されます。
固定配置の注意点
ただし、position: sticky
はパララックスの仕組みを変更します。スティッキー ポジショニングでは、要素をスクロール コンテナに固定しようとしますが、スティッキー以外のバージョンではそうしません。つまり、スティッキーがあるパララックスの場合、スティッキーがないパララックスとは逆になります。
position: sticky
では、要素が z=0 に近づくほど移動が少なくなります。position: sticky
を使用しない場合は、z=0 に近づくほど要素が移動します。
少し抽象的な印象がある方は、Robert Flack によるこちらのデモをご覧ください。固定配置を使用した場合とない場合で要素の動作がどのように異なるかを示しています。違いを確認するには、Chrome Canary(執筆時点ではバージョン 56)または Safari が必要です。
position: sticky
が視差スクロールに及ぼす影響を示す Robert Flack のデモ。
さまざまなバグと回避策
ただし、他の方法と同様に、まだ調整が必要な点があります。
- スティッキーのサポートが一貫していない。Chrome はまだサポートが実装されており、Edge では完全にサポートされていません。Firefox では、スティッキーが視点変換と組み合わされた場合に、描画バグが発生します。そのような場合は、必要なときだけ
position: sticky
(-webkit-
プレフィックス付きバージョン)を追加する簡単なコードを追加することをおすすめします。これは Mobile Safari 専用です。 - この効果は Edge で「ただ機能する」わけではありません。Edge は OS レベルでスクロールを処理しようとします。これは通常は良いことですが、この場合はスクロール中の視点の変化を検出できなくなります。これを修正するには、固定位置の要素を追加します。これは Edge を OS 以外のスクロール方法に切り替えているように見え、視点の変化を考慮するようにします。
- 「ページのコンテンツが巨大になりました!」多くのブラウザは、ページのコンテンツのサイズを決定する際にスケールを考慮しますが、残念ながら Chrome と Safari は遠近感を考慮しません。たとえば、要素に 3 倍のスケールが適用されている場合、
perspective
が適用された後に要素が 1 倍になっていても、スクロールバーなどが表示されます。この問題を回避するには、要素を右下からスケーリングします(transform-origin: bottom right
を使用)。これにより、サイズが大きい要素がスクロール可能な領域の「負の領域」(通常は左上)に拡大されます。スクロール可能な領域では、負の領域のコンテンツを表示またはスクロールすることはできません。
まとめ
パララックス効果は、よく考えたうえで使用すると楽しい効果になります。ご覧のとおり、パフォーマンスに優れ、スクロールに連動した、クロスブラウザ対応の方法で実装できます。望ましい効果を得るには、少し複雑な数学と少量のボイラープレートが必要になるため、小さなヘルパー ライブラリとサンプルをまとめました。これは UI 要素サンプルの GitHub リポジトリで確認できます。
ぜひお試しいただき、ご感想をお寄せください。