ビューポートが複数あるとしたらどうでしょう。
BRRRRAAAAAAAMMMMMMMMMM
現在使用しているビューポートは、実際にはビューポート内のビューポートです。
BRRRRAAAAAAAMMMMMMMMMM
また、DOM から取得したデータが、そのうちの 1 つのビューポートを指している場合もあります。
BRRRRAAAAM… えっ、何?
以下をご覧ください。
レイアウト ビューポートとビジュアル ビューポート
上の動画では、ウェブページのスクロールとピンチズームが行われています。右側のミニマップには、ページ内のビューポートの位置が表示されています。
通常のスクロールでは、緑色の領域は、position: fixed
アイテムが貼り付けられるレイアウト ビューポートを表します。
ピンチ操作でズームすると、動作がおかしくなります。赤いボックスはビジュアル ビューポートを表します。これは、実際に表示されるページの部分です。このビューポートは移動できますが、position: fixed
要素はレイアウト ビューポートに接続されたまま、元の位置に留まります。レイアウト ビューポートの境界でパンすると、レイアウト ビューポートも一緒にドラッグされます。
互換性の向上
残念ながら、Web 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
をリッスンする場合と同様に、結果としてなんらかの「更新」関数を呼び出すことになります。ただし、これらのイベントの多くは同時に発生することが一般的です。ユーザーがウィンドウのサイズを変更すると、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 です。