隆重推出 VisionViewport

阿奇巴德 (Jake Archibald)
Jake Archibald

我告訴你一個可視區域有多個可視區域。

巴西雷亞爾 AAAAAAMMMMMMMM

您目前使用的可視區域,實際上是可視區域內的可視區域。

巴西雷亞爾 AAAAAAMMMMMMMM

有時候,DOM 提供的資料會參照其中一個可視區域,而非另一個可視區域。

BRRRRAAAAM...等什麼?

我們來看看:

版面配置可視區域與可視區域

上方影片顯示網頁捲動和雙指撥動縮放的畫面,右側是顯示可視區域位置的迷你地圖。

一般的捲動過程中,所有情況都一向朝向前方。綠色區域代表「版面配置可視區域」,也就是 position: fixed 項目固定的位置。

使用雙指撥動縮放功能時,發生異常狀況。紅色方塊代表「視覺可視區域」,這是我們實際看到的網頁部分。此可視區域可以移動,而 position: fixed 元素保持在所在位置,且附加至版面配置可視區域。如果在版面配置可視區域的邊界平移,它會一併拖曳版面配置可視區域。

提升相容性

遺憾的是,網路 API 與自身參照的可視區域不同,而且在不同瀏覽器上也不一致。

舉例來說,element.getBoundingClientRect().y 會傳回版面配置可視區域中的偏移值。這很不錯,但我們通常希望在頁面中的位置,因此撰寫:

element.getBoundingClientRect().y + window.scrollY

不過,許多瀏覽器都會針對 window.scrollY 使用視覺可視區域,這表示使用者以雙指撥動縮放時,上述程式碼會中斷。

Chrome 61 會將 window.scrollY 變更為參照版面配置可視區域,這表示上述程式碼即使雙指撥動縮放也能正常運作。事實上,瀏覽器會稍微變更所有位置屬性,以參照版面配置可視區域。

除一個新資源以外...

向指令碼公開可視區域

新的 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。這個問題不會受到 devicePixelRatio 的影響。

此外還有幾個事件:

window.visualViewport.addEventListener('resize', listener);
visualViewport 項活動
resize widthheightscale 變更時觸發。
scroll offsetLeftoffsetTop 變更時觸發。

示範模式

本文開頭的影片是利用visualViewport 的製作而成,別忘了在 Chrome 61 以上版本查看。其功能利用visualViewport讓迷你地圖固定在視覺可視區域右上方,並且一律以相同的縮放比例顯示。

我瞭解了

只有在視覺可視區域變更時才會觸發事件

這似乎是顯而易見的事,但當我第一次使用 visualViewport 的時候就學到了。

如果版面配置可視區域可以調整大小,但可視區域沒有,就不會收到 resize 事件。不過,如果版面配置可視區域沒有改變寬度/高度,版面配置可視區域往往也會改變。

真正需要的是捲動頁面。如果進行捲動,但視覺可視區域保持靜態,相對於版面配置可視區域,則在 visualViewport 上就不會出現 scroll 事件,這是很常見的情況。在一般文件捲動期間,可視區域會鎖定在版面配置可視區域左上角,因此 scroll「不會觸發」visualViewport 上。

如要瞭解視覺可視區域的所有變更 (包括 pageToppageLeft),則必須監聽視窗的捲動事件:

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

避免使用多個事件監聽器重複作業

與在視窗中監聽 scrollresize 類似,您可能會以結果呼叫某種「更新」函式。不過,這類事件往往同時發生。如果使用者調整視窗大小,則會觸發 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!');

請改採以下做法:

正確做法

Works – 使用事件監聽器

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

偏移值會四捨五入

我認為 (也希望) 是另一個 Chrome 錯誤

offsetLeftoffsetTop 會四捨五入,一旦使用者放大,就會相當不準確。您可以在示範模式中看到這個現象的問題;如果使用者放大或慢慢平移,系統會在未縮放的像素間傳送迷你地圖

事件發生率緩慢

和其他 resizescroll 事件一樣,這些事件不會觸發每個影格,尤其是在行動裝置上。您可以在示範環境中看到這一點;雙指撥動縮放後,迷你地圖會無法鎖定可視區域。

無障礙功能

示範資源中,我使用 visualViewport 因應使用者的雙指撥動縮放。這部分示範很合理,但在進行會覆寫使用者想放大的任何項目之前,請務必謹慎考慮。

visualViewport 可用來改善無障礙體驗。舉例來說,如果使用者放大畫面,您可以選擇隱藏裝飾性 position: fixed 項目,以便隱藏使用者。但再次提醒您,您沒有隱藏使用者想查看的內容。

建議您在使用者放大畫面時,將貼文發布至數據分析服務。這樣做可協助您在預設縮放等級下,找出使用者遇到問題的網頁。

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

大功告成!visualViewport 是很好的 API,可一併解決相容性問題。