我告訴你一個可視區域有多個可視區域。
巴西雷亞爾 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
|
當 width 、height 或 scale 變更時觸發。 |
scroll
|
當 offsetLeft 或 offsetTop 變更時觸發。 |
示範模式
本文開頭的影片是利用visualViewport
的製作而成,別忘了在 Chrome 61 以上版本查看。其功能利用visualViewport
讓迷你地圖固定在視覺可視區域右上方,並且一律以相同的縮放比例顯示。
我瞭解了
只有在視覺可視區域變更時才會觸發事件
這似乎是顯而易見的事,但當我第一次使用 visualViewport
的時候就學到了。
如果版面配置可視區域可以調整大小,但可視區域沒有,就不會收到 resize
事件。不過,如果版面配置可視區域沒有改變寬度/高度,版面配置可視區域往往也會改變。
真正需要的是捲動頁面。如果進行捲動,但視覺可視區域保持靜態,相對於版面配置可視區域,則在 visualViewport
上就不會出現 scroll
事件,這是很常見的情況。在一般文件捲動期間,可視區域會鎖定在版面配置可視區域左上角,因此 scroll
「不會觸發」在 visualViewport
上。
如要瞭解視覺可視區域的所有變更 (包括 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!');
請改採以下做法:
Works – 使用事件監聽器
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,可一併解決相容性問題。