如果我告訴你,有超過一個檢視區,你會怎麼做?
BRRRRAAAAAAAMMMMMMMMMM
而您目前使用的可視區域,其實是可視區域中的可視區域。
BRRRRAAAAAAAMMMMMMMMMM
有時 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!');
請改採以下做法:
可行 - 使用事件監聽器
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,可解決相容性問題。