ResizeObserver:it';例如適用於元素的 document.onresize

ResizeObserver 可讓您知道元素大小有無變更。

ResizeObserver 之前,您必須將事件監聽器附加至文件的 resize 事件,才能收到視區大小變更的通知。在事件處理常式中,您必須找出哪些元素受到該變更的影響,並呼叫特定日常安排來做出適當回應。如果您需要在調整大小後取得元素的新尺寸,就必須呼叫 getBoundingClientRect()getComputedStyle(),但如果您未處理「所有」讀取和「所有」寫入作業的批次作業,就可能會導致版面配置資源浪費。

這甚至不適用於元素變更大小,但主視窗未變更大小的情況。舉例來說,附加新子項、將元素的 display 樣式設為 none,或類似的動作,都可能會變更元素、元素的兄弟姊妹或祖先的大小。

這就是 ResizeObserver 實用的原因。無論變更原因為何,它都會回應任何觀察元素的大小變更。也可以存取觀察到的元素的新大小。

瀏覽器支援

  • Chrome:64。
  • Edge:79,
  • Firefox:69。
  • Safari:13.1.

資料來源

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 物件。內容方塊是可放置內容的方塊。它是邊框框框減去邊框間距。

CSS 方塊模型圖表。

請務必注意,雖然 ResizeObserver 回報 contentRect 的尺寸和邊框間距,但它只會監控 contentRect請勿contentRect 與元素的邊界框搞混。getBoundingClientRect() 所回報的邊界框,是包含整個元素及其子項的框框。SVG 是這項規則的例外狀況,ResizeObserver 會回報邊界框的尺寸。

自 Chrome 第 84 版起,ResizeObserverEntry 有三個新屬性,可以提供更詳細的資訊。每個屬性都會傳回 ResizeObserverSize 物件,其中包含 blockSize 屬性和 inlineSize 屬性。這項資訊是關於在叫用回呼時觀察到的元素。

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

所有這些項目都會傳回唯讀陣列,因為我們希望日後這些項目能夠支援具有多個片段的元素,而這類情況會發生在多欄情境中。目前,這些陣列只會包含一個元素。

這些屬性在平台上的支援有限,但 Firefox 已支援前兩個屬性。

何時會收到這項通知?

規格說明書規定 ResizeObserver 應在繪製前和版面配置後處理所有調整大小事件。如此一來,ResizeObserver 的回呼就是變更網頁版面配置的理想位置。由於 ResizeObserver 處理作業會在版面配置和繪製之間進行,因此這麼做只會使版面配置失效,而非繪製。

我瞭解了

您可能會好奇:如果變更 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,導致轉譯時間延遲。