複数のビューポートがあるとしたらどうでしょうか
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
|
width 、height 、または scale が変更されたときに発生します。 |
scroll
|
offsetLeft または offsetTop が変更されたときに発生します。 |
デモ
この記事の冒頭の動画は、visualViewport
を使用して作成されました。Chrome 61 以降でご覧ください。visualViewport
により、ミニマップがビジュアル ビューポートの右上に固定され、逆スケールで常に同じサイズに見えるようになります(ピンチズームにかかわらず)。
解決済み
イベントは、ビジュアル ビューポートが変更された場合にのみ発生します
当然のことのように感じますが、visualViewport
で初めてプレイしたときに気づきました。
レイアウト ビューポートがサイズ変更されても、ビジュアル ビューポートはサイズ変更されない場合、resize
イベントは発生しません。ただし、ビジュアル ビューポートの幅や高さも変更せずにレイアウト ビューポートのサイズを変更することは珍しくありません。
一番の問題点はスクロールです。スクロールが発生しても、ビジュアル ビューポートがレイアウト ビューポートに対して静的のままである場合、visualViewport
で scroll
イベントは発生しません。これはよくあることです。通常のドキュメントのスクロール中、ビジュアル ビューポートはレイアウト ビューポートの左上にロックされたままになるため、visualViewport
では scroll
は起動しません。
pageTop
や pageLeft
など、ビジュアル ビューポートに対するすべての変更を聞くには、ウィンドウのスクロール イベントもリッスンする必要があります。
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
複数のリスナーで処理の重複を回避する
ウィンドウで scroll
と resize
をリッスンする場合と同様に、結果としてなんらかの「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 の別のバグだと思います。
offsetLeft
と offsetTop
は四捨五入されるため、ユーザーがズームインすると精度は低くなります。これについては、デモで問題を確認できます。ユーザーがズームインやパンをゆっくりと操作すると、ミニマップはズームされていないピクセル間をスナップします。
イベントレートが遅い
他の resize
イベントや scroll
イベントと同様に、これらがフレームごとに発生するとは限りません(特にモバイルの場合)。これは、デモでわかります。ピンチズームでミニマップがビューポートにロックされたままになることはありません。
ユーザー補助
デモでは、visualViewport
を使用してユーザーのピンチズームに対抗しました。この特定のデモでは理にかなっていますが、拡大したいというユーザーの要望を無視するような操作を行う前に、慎重に検討する必要があります。
visualViewport
を使用すると、ユーザー補助機能を改善できます。たとえば、ユーザーがズームインしている場合、装飾的な position: fixed
アイテムを非表示にして、ユーザーの邪魔にならないようにすることができます。繰り返しになりますが、ユーザーが詳しく調べようとしているものを隠さないよう注意してください。
ユーザーが拡大縮小したときに分析サービスに投稿することを検討してください。これは、デフォルトのズームレベルでユーザーが使いにくいページを特定するのに役立ちます。
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
以上で終了です。visualViewport
は、互換性の問題を解決する小さな API です。