ResizeObserver
可在元素大小變更時通知您。
在 ResizeObserver
之前,您必須將事件監聽器附加至文件的 resize
事件,才能在可視區域尺寸發生變化時收到通知。在事件處理常式中,您必須找出受到該變更影響的元素,並呼叫特定常式以適當回應。如果您在調整大小後需要元素的新尺寸,就必須呼叫 getBoundingClientRect()
或 getComputedStyle()
,如果不處理「所有」讀取和「所有」寫入作業,這可能會導致版面配置失敗。
這甚至並未涵蓋元素在沒有主視窗調整大小的情況下變更元素大小的情況。舉例來說,如果為新的子項附加新的子項、將元素的 display
樣式設為 none
,或是類似的動作,就會改變元素的大小、同層級或其祖系。
這就是為什麼 ResizeObserver
是實用的基元。這個函式會回應觀察到的元素大小變化,與導致變更的原因無關。也可以存取觀察到元素的新大小。
API
上述 Observer
後置字串的所有 API 都共用簡單的 API 設計。ResizeObserver
也不例外。建立 ResizeObserver
物件,並將回呼傳遞至建構函式。系統會向回呼傳遞 ResizeObserverEntry
物件的陣列 (每個觀察到的元素各有一個項目),其中包含元素的新尺寸。
var ro = new ResizeObserver(entries => {
for (let entry of entries) {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
}
});
// Observe one or multiple elements
ro.observe(someElement);
部分詳細資料
系統會回報哪些資料?
一般來說,ResizeObserverEntry
會透過名為 contentRect
的屬性回報元素的內容方塊,進而回傳 DOMRectReadOnly
物件。內容方塊是用來放置內容的方塊。其為邊框方塊減去邊框間距。
請注意,雖然 ResizeObserver
會回報 contentRect
和邊框間距的維度,但只會「監控」 contentRect
。請勿將 contentRect
與元素的定界框混淆。getBoundingClientRect()
回報的定界框就是包含整個元素及其子系的方塊。SVG 是規則的例外狀況,其中 ResizeObserver
會回報定界框的尺寸。
自 Chrome 84 版起,ResizeObserverEntry
有三個新屬性來提供更多詳細資訊。這些屬性每個都會傳回一個 ResizeObserverSize
物件,其中包含 blockSize
屬性和 inlineSize
屬性。這項資訊是關於叫用回呼時觀察到的元素。
borderBoxSize
contentBoxSize
devicePixelContentBoxSize
這些所有項目都會傳回唯讀陣列,因為未來,希望未來能支援具有多個片段的元素 (發生多欄情境)。這些陣列目前只會包含一個元素。
這些屬性的平台支援有限,但 Firefox 已支援前兩項屬性。
報告何時會遭到檢舉?
規格指示 ResizeObserver
應在繪製之前和版面配置後處理所有大小調整事件。如此一來,收到 ResizeObserver
的回呼是變更頁面版面配置的絕佳位置。由於 ResizeObserver
處理作業會在版面配置和繪製之間進行,因此只會使版面配置失效,不會影響繪製效果。
我瞭解了
您可能會問自己:如果我將回呼中觀察到的元素大小變更為 ResizeObserver
,會發生什麼事?答案是:系統會立即觸發另一次回呼呼叫。幸好,ResizeObserver
的機制可避免無限回呼迴圈和循環依附元件。只有在大小調整過的元素在 DOM 樹狀結構中較先前回呼中處理的「shallowest」元素更深層時,系統才會在同一頁框中處理變更。否則系統將延後到下一個影格。
應用程式
ResizeObserver
可讓您實作每個元素媒體查詢。藉由觀察元素,您可以透過動畫方式定義設計中斷點並變更元素的樣式。在以下範例中,第二個方塊將根據寬度變更邊框半徑。
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
entry.target.style.borderRadius =
Math.max(0, 250 - entry.contentRect.width) + 'px';
}
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));
另一個有趣的例子是即時通訊視窗。典型的由上而下對話版面配置的問題,就是捲動位置。為避免讓使用者感到困惑,建議讓視窗固定在對話底部,也就是最新的訊息顯示位置。此外,任何類型的版面配置變更 (例如從橫向或直向的手機) 也應達到相同的效果。
ResizeObserver
可讓您編寫「單一」程式碼,用來處理「這兩種」情況。「調整視窗大小」是指 ResizeObserver
可依定義擷取的事件,但呼叫 appendChild()
也會調整元素的大小 (除非已設定 overflow: hidden
),因為需要為新元素騰出空間。因此,建構所需效果只需幾行程式碼:
const ro = new ResizeObserver(entries => {
document.scrollingElement.scrollTop =
document.scrollingElement.scrollHeight;
});
// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);
// Observe the timeline to process new messages
ro.observe(timeline);
很整潔,對吧?
您可以在這裡加入更多程式碼,處理使用者手動向上捲動,並想要在有新訊息時捲動查看「該」訊息的情況。
另一個用途是任何類型的自訂元素,與其擁有自己的版面配置。在 ResizeObserver
之前,在尺寸變更時,一直無法獲得可靠的通知,以便再次版面配置子項。
對下一個顯示內容 (INP) 互動的影響
與下一個顯示的內容互動 (INP) 指標可以評估網頁與使用者互動的整體回應速度。如果網頁的 INP 處於「良好」門檻 (也就是 200 毫秒以下),就表示網頁可以穩定回應使用者與網頁的互動。
雖然事件回呼為回應使用者互動而執行所需的時間可能會大幅影響互動的總延遲時間,但這並不是 INP 的唯一考量因素。INP 也會考量互動的「下一個繪製」所需的時間。此為顯示回應完成互動而更新使用者介面所需的時間。
在 ResizeObserver
的考量範圍內,這一點很重要,因為 ResizerObserver
執行個體執行的回呼會在轉譯工作「之前」發生。在設計上,由於回呼中發生的工作必須納入考量,因為這項作業的最終可能需要變更使用者介面。
請盡量減少轉譯作業,盡量減少 ResizeObserver
回呼中的轉譯工作,因為過度轉譯工作可能會造成瀏覽器延遲執行重要工作的情況。舉例來說,如有任何互動的回呼會執行 ResizeObserver
回呼,請務必執行以下操作,盡可能提供最流暢的使用體驗:
- 請務必盡量簡化 CSS 選取器,以避免過多樣式重新計算工作。系統會在版面配置前進行樣式重新計算,複雜的 CSS 選取器可能會延遲版面配置作業。
- 請避免在
ResizeObserver
回呼中執行任何可能會觸發強制重排的工作。 - 更新網頁版面配置所需的時間通常會隨著網頁上的 DOM 元素數量增加。雖然網頁是否使用
ResizeObserver
都是如此,但由於頁面結構的複雜度增加,ResizeObserver
回呼中完成的工作可能會變得格外重要。
結語
所有主要瀏覽器都能使用 ResizeObserver
,可讓您有效率地監控元素層級的元素大小調整情形。請注意,有了這個強大的 API,不要延遲算繪太多時間。