パフォーマンスに優れたパララックス

パララックス効果は、好き嫌いは別として、今後も使われ続けるでしょう。適切に使用すると、ウェブアプリに深みと繊細さを加えることができます。ただし、パフォーマンスの高い方法で視差効果を実装するのは難しい場合があります。この記事では、パフォーマンスが優れており、同じくらい重要なこととして、クロスブラウザで動作するソリューションについて説明します。

パララックスのイラスト。

要約

  • スクロール イベントや background-position を使用してパララックス アニメーションを作成しないでください。
  • CSS 3D 変換を使用して、より正確なパララックス効果を作成します。
  • Mobile Safari では、position: sticky を使用して、視差効果が伝播されるようにします。

ドロップイン ソリューションが必要な場合は、UI 要素のサンプル GitHub リポジトリにアクセスして、視差ヘルパー JS を取得してください。GitHub リポジトリでパララックス スクロールのライブデモをご覧いただけます。

問題のある視差効果

まず、視差効果を実現する 2 つの一般的な方法と、特にそれらがこの目的に適していない理由を見てみましょう。

悪い例: スクロール イベントを使用する

視差効果の重要な要件は、スクロールと連動していることです。ページのスクロール位置が変化するたびに、視差効果要素の位置が更新される必要があります。これは単純なことのように思えますが、最新のブラウザの重要なメカニズムは、非同期で動作する機能です。この場合、スクロール イベントに適用されます。ほとんどのブラウザでは、スクロール イベントは「ベスト エフォート」として配信され、スクロール アニメーションのすべてのフレームで配信されるとは限りません。

この重要な情報から、スクロール イベントに基づいて要素を移動する JavaScript ベースのソリューションを避ける必要がある理由がわかります。JavaScript では、視差効果がページのスクロール位置と同期することを保証できません。以前のバージョンのモバイル Safari では、スクロール イベントはスクロールの最後に配信されていたため、JavaScript ベースのスクロール効果を作成することはできませんでした。新しいバージョンでは、アニメーション中にスクロール イベントが配信されますが、Chrome と同様に「ベスト エフォート」ベースです。メインスレッドが他の作業でビジー状態の場合、スクロール イベントはすぐに配信されず、視差効果が失われます。

悪い例: background-position を更新しています

もう 1 つ避けたいのは、すべてのフレームでペイントすることです。多くのソリューションでは、視差効果を実現するために background-position の変更を試みますが、これにより、ブラウザはスクロール時にページの影響を受ける部分を再描画することになり、アニメーションが大幅にジャンプするほどコストがかかる可能性があります。

視差効果の約束を果たすには、高速化されたプロパティとして適用でき(現在は変換と不透明度のみ)、スクロール イベントに依存しないものが必要です。

CSS in 3D

Scott KellumKeith Clark は、CSS 3D を使用して視差効果を実現する分野で重要な業績を上げています。彼らが使用する手法は、実質的に次のとおりです。

  • overflow-y: scroll(およびおそらく overflow-x: hidden)でスクロールするコンテナ要素を設定します。
  • 同じ要素に perspective 値を適用し、perspective-origintop left または 0 0 に設定します。
  • その要素の子に 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)だけ上に変換されます。

ただし、スクロール要素にパースペクティブ値を適用すると、このプロセスが混乱します。スクロール変換の基盤となる行列が変更されます。perspectivetranslateZ の値によっては、300 ピクセルのスクロールで子要素が 150 ピクセルしか移動しないことがあります。要素の translateZ 値が 0 の場合、1:1 でスクロールされます(以前と同様)。ただし、パースペクティブの原点から Z 方向にプッシュされた子要素は、異なるレートでスクロールされます。最終的な結果: 視差効果のある動き。また、非常に重要なこととして、これはブラウザの内部スクロール機構の一部として自動的に処理されます。つまり、scroll イベントをリッスンしたり、background-position を変更したりする必要はありません。

モバイル Safari の問題

すべての効果には注意点があり、変換に関する重要な注意点の 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 ピクセルで計算された場合、その 300 ピクセルのオフセット値を、スティッキー要素に適用する前に、パースペクティブ(またはその他の変換)を使用して操作する新しい機会が生まれます。

position: -webkit-sticky を視差効果のある要素に適用することで、-webkit-overflow-scrolling: touch の平坦化効果を効果的に「逆転」させることができます。これにより、視差効果要素がスクロール ボックスを持つ最も近い祖先(この場合は .container)を参照します。次に、前と同様に、.parallax-containerperspective 値を適用し、計算されたスクロール オフセットを変更して視差効果を作成します。

<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);
}

これにより、モバイル Safari のパララックス効果が復元されます。これは、すべてのユーザーにとって素晴らしいニュースです。

固定位置の注意点

ただし、position: sticky は視差のメカニズムを変更する点が異なります。スティッキー ポジショニングは、要素をスクロール コンテナに固定しようとしますが、スティッキーでないバージョンは固定しません。つまり、スティッキー エンドのある視差は、スティッキー エンドのない視差の逆になります。

  • position: sticky を使用すると、要素が z=0 に近いほど、移動量が少なくなります。
  • position: sticky がない場合、要素が z=0 に近いほど、要素は大きく移動します。

これらが少し抽象的に感じられる場合は、Robert Flack によるこちらのデモをご覧ください。スティッキー ポジショニングの有無によって要素の動作がどのように異なるかを示しています。この違いを確認するには、Chrome Canary(執筆時点ではバージョン 56)または Safari が必要です。

視差効果のスクリーンショット

Robert Flack によるデモposition: sticky が視差スクロールに与える影響を示しています。

さまざまなバグと回避策

ただし、他のことと同様に、まだ解決すべき問題がいくつかあります。

  • スティッキーのサポートに一貫性がない。Chrome ではまだ実装中であり、Edge ではまったくサポートされていません。Firefox では、sticky とパースペクティブ変換を組み合わせると描画バグが発生します。このような場合は、必要なとき(Mobile Safari のみ)に position: sticky-webkit- 接頭辞付きバージョン)のみを追加するコードを少し追加する価値があります。
  • Edge では、この効果は「そのままでは機能しません」。Edge は OS レベルでスクロールを処理しようとします。これは一般的には良いことですが、このケースではスクロール中の視点の変化を検出できなくなります。この問題を解決するには、固定位置要素を追加します。これにより、Edge が OS 以外のスクロール方法に切り替わり 、遠近法の変化が考慮されるようになります。
  • 「ページの内容が大きくなりました」多くのブラウザは、ページのコンテンツの大きさを決定する際にスケールを考慮しますが、残念ながら Chrome と Safari はパースペクティブを考慮しません。たとえば、要素に 3 倍のスケールが適用されている場合、perspective が適用された後の要素が 1 倍であっても、スクロールバーなどが表示されることがあります。この問題は、右下隅から要素をスケーリングする(transform-origin: bottom right を使用)ことで回避できます。これは、大きすぎる要素がスクロール可能な領域の「負の領域」(通常は左上)に拡大されるためです。スクロール可能な領域では、負の領域のコンテンツを表示したり、スクロールしたりすることはできません。

まとめ

パララックスは、慎重に使用すれば楽しい効果です。ご覧のとおり、パフォーマンスが高く、スクロール連動型で、クロスブラウザ対応の形で実装できます。数学的な操作と、目的の効果を得るためのボイラープレートが少し必要になるため、小さなヘルパー ライブラリとサンプルをまとめました。これらは UI 要素のサンプル GitHub リポジトリで確認できます。

ぜひお試しいただき、結果をお知らせください。