visualViewport の導入

Jake Archibald 氏
Jake Archibald

複数のビューポートがあるとしたらどうでしょうか

BRRRRAAAAAMMMMMMMMMM

現在使用しているビューポートは、実際にはビューポート内のビューポートです。

BRRRRAAAAAMMMMMMMMMM

DOM から提供されるデータは、一方のビューポートを参照し、他方を参照しないこともあります。

BRRRRAAAAM...ちょっと待って?

本当です。参考にしてください。

レイアウト ビューポートとビジュアル ビューポート

上の動画では、ウェブページがスクロールおよびピンチズームされ、右側にページ内のビューポートの位置を示すミニマップが表示されています。

通常のスクロールでは、処理が非常に簡単です。緑色の領域はレイアウト ビューポートを表し、position: fixed アイテムが固定されています。

ピンチズームを導入すると不思議な現象が起きます。赤いボックスは視覚的なビューポート、つまり実際に見ることができるページの一部を表しています。このビューポートは、position: fixed 要素がレイアウト ビューポートにアタッチされたままで、移動できます。レイアウト ビューポートの境界でパンすると、レイアウト ビューポートも一緒にドラッグされます。

互換性の向上

残念ながら、ウェブ API には参照するビューポートの点やブラウザ間で一貫性がありません。

たとえば、element.getBoundingClientRect().yレイアウト ビューポート内のオフセットを返します。これはよくありますが、ページ内の位置が必要なことが多いため、次のように記述します。

element.getBoundingClientRect().y + window.scrollY

しかし、多くのブラウザは window.scrollYビジュアル ビューポートを使用します。つまり、ユーザーがピンチズームを行うと上記のコードが機能しなくなります。

Chrome 61 では、代わりにレイアウト ビューポートを参照するように window.scrollY が変更されます。つまり、上記のコードはピンチズームでも機能します。実際、ブラウザはレイアウト ビューポートを参照するように、すべての位置プロパティを徐々に変更しています。

1 つの新しいプロパティを除き...

スクリプトにビジュアル ビューポートを公開する

新しい API では、ビジュアル ビューポートを window.visualViewport として公開します。これはドラフト仕様で、複数のブラウザ間で承認されたもので、Chrome 61 で実装されています。

console.log(window.visualViewport.width);

window.visualViewport で提供されるものは次のとおりです。

visualViewport 件の宿泊施設
offsetLeft ビジュアル ビューポートの左端とレイアウト ビューポートの距離(CSS ピクセル単位)。
offsetTop ビジュアル ビューポートの上端とレイアウト ビューポートの距離(CSS ピクセル単位)。
pageLeft ビジュアル ビューポートの左端とドキュメントの左境界の距離(CSS ピクセル単位)。
pageTop ビジュアル ビューポートの上端とドキュメントの上境界の距離(CSS ピクセル単位)。
width 表示ビューポートの幅(CSS ピクセル)。
height 表示ビューポートの高さ(CSS ピクセル)。
scale ピンチズームで適用されるスケール。ズームによりコンテンツが 2 倍のサイズになった場合は、2 が返されます。これは devicePixelRatio の影響を受けません。

他にも次のようなイベントがあります。

window.visualViewport.addEventListener('resize', listener);
visualViewport 件のイベント
resize widthheight、または scale が変更されたときに発生します。
scroll offsetLeft または offsetTop が変更されたときに発生します。

デモ

この記事の冒頭の動画は、visualViewport を使用して作成されました。Chrome 61 以降でご覧くださいvisualViewport により、ミニマップがビジュアル ビューポートの右上に固定され、逆スケールで常に同じサイズに見えるようになります(ピンチズームにかかわらず)。

解決済み

イベントは、ビジュアル ビューポートが変更された場合にのみ発生します

当然のことのように感じますが、visualViewport で初めてプレイしたときに気づきました。

レイアウト ビューポートがサイズ変更されても、ビジュアル ビューポートはサイズ変更されない場合、resize イベントは発生しません。ただし、ビジュアル ビューポートの幅や高さも変更せずにレイアウト ビューポートのサイズを変更することは珍しくありません。

一番の問題点はスクロールです。スクロールが発生しても、ビジュアル ビューポートがレイアウト ビューポートに対して静的のままである場合、visualViewportscroll イベントは発生しません。これはよくあることです。通常のドキュメントのスクロール中、ビジュアル ビューポートはレイアウト ビューポートの左上にロックされたままになるため、visualViewport では scroll は起動しません

pageToppageLeft など、ビジュアル ビューポートに対するすべての変更を聞くには、ウィンドウのスクロール イベントもリッスンする必要があります。

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

複数のリスナーで処理の重複を回避する

ウィンドウで scrollresize をリッスンする場合と同様に、結果としてなんらかの「update」関数が呼び出される可能性があります。ただし、これらのイベントの多くが同時に発生することは珍しくありません。ユーザーがウィンドウのサイズを変更すると resize がトリガーされますが、多くの場合 scroll もトリガーします。パフォーマンスを向上させるには、変更を複数回処理しないでください。

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

単一の update イベントなど、よりよい方法があると思われるため、これについて仕様の問題を報告しました

イベント ハンドラが動作しない

Chrome のバグにより、これは機能しません

すべきでないこと

バグが多い - イベント ハンドラを使用します。

visualViewport.onscroll = () => console.log('scroll!');

その場合は次の方法を試してください。

推奨事項

機能 - イベント リスナーを使用します。

visualViewport.addEventListener('scroll', () => console.log('scroll'));

オフセット値は四捨五入されます

これは Chrome の別のバグだと思います。

offsetLeftoffsetTop は四捨五入されるため、ユーザーがズームインすると精度は低くなります。これについては、デモで問題を確認できます。ユーザーがズームインやパンをゆっくりと操作すると、ミニマップはズームされていないピクセル間をスナップします。

イベントレートが遅い

他の resize イベントや scroll イベントと同様に、これらがフレームごとに発生するとは限りません(特にモバイルの場合)。これは、デモでわかります。ピンチズームでミニマップがビューポートにロックされたままになることはありません。

ユーザー補助

デモでは、visualViewport を使用してユーザーのピンチズームに対抗しました。この特定のデモでは理にかなっていますが、拡大したいというユーザーの要望を無視するような操作を行う前に、慎重に検討する必要があります。

visualViewport を使用すると、ユーザー補助機能を改善できます。たとえば、ユーザーがズームインしている場合、装飾的な position: fixed アイテムを非表示にして、ユーザーの邪魔にならないようにすることができます。繰り返しになりますが、ユーザーが詳しく調べようとしているものを隠さないよう注意してください。

ユーザーが拡大縮小したときに分析サービスに投稿することを検討してください。これは、デフォルトのズームレベルでユーザーが使いにくいページを特定するのに役立ちます。

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

以上で終了です。visualViewport は、互換性の問題を解決する小さな API です。