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
處理作業會在版面配置和繪製之間進行,因此這麼做只會使版面配置失效,而非繪製。
Gotcha
您可能會想知道:如果我將回呼中觀察到的元素大小變更為 ResizeObserver
,會發生什麼事?答案是:您會立即觸發對回呼的另一個呼叫。幸好,ResizeObserver
有一種機制可避免無限的回呼迴圈和循環相依性。只有在重新調整大小的元素在 DOM 樹狀結構中比先前回呼中處理的最淺元素更深時,系統才會在同一個影格中處理變更。否則,會延遲至下一個影格。
應用程式
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
之前,沒有可靠的方式可在其尺寸變更時收到通知,以便重新安排子項。
對 Interaction to Next Paint (INP) 的影響
Interaction to Next Paint (INP) 是一種指標,用於評估網頁回應使用者互動的整體效能。如果網頁的 INP 分數達到「良好」門檻 (即 200 毫秒以下),表示網頁可可靠地回應使用者與其互動。
雖然事件回呼在回應使用者互動時所需的時間,可能會大幅影響互動的總延遲時間,但這並不是 INP 唯一需要考量的層面。INP 也會考量下一個顯示的內容互動發生所需的時間。這是在回應互動完成時,更新使用者介面所需的算繪作業所需的時間。
就 ResizeObserver
而言,這一點非常重要,因為 ResizerObserver
例項執行的回呼會在運算工作前發生。這是設計上的考量,因為必須考量在回呼中發生的工作,因為該工作產生的結果很可能需要變更使用者介面。
請務必在 ResizeObserver
回呼中盡可能減少轉譯工作,因為轉譯工作過多可能導致瀏覽器延遲執行重要工作。舉例來說,如果任何互動都有回呼,導致 ResizeObserver
回呼執行,請務必執行下列操作,以便提供最順暢的體驗:
- 請確保 CSS 選取器盡可能簡單,以免避免過度重複樣式計算作業。樣式會在版面配置前重新計算,而複雜的 CSS 選取器可能會延遲版面配置作業。
- 請避免在
ResizeObserver
回呼中執行任何可能觸發強制重新流動的作業。 - 更新網頁版面配置所需的時間通常會隨著網頁上的 DOM 元素數量而增加。無論網頁是否使用
ResizeObserver
,這項原則都適用,但隨著網頁結構複雜度增加,在ResizeObserver
回呼中完成的工作可能會變得相當繁重。
結論
ResizeObserver
可在所有主要瀏覽器中使用,並提供一種有效的方式,可在元素層級監控元素大小調整。請小心,別讓這項強大的 API 導致轉譯時間過長。