MutationObserver - DOM 宇宙的觀察者

alexianalexian
2022-11-19
JavaScriptMutationObserverobserver pattern

你不看我怎麼知道我看你?

網頁前端開發與日俱進,不斷有新的 web API 出現,為前端開發帶來更多不同的可能,今天要為大家介紹的是 MutationObserver (MDN)。

MutationObserver 可以在不相依其他監聽事件的情況下,獨立觀察元素內的節點、屬性及文字內容的變化。它有一個前身:Mutation Event,但由於效能等因素被棄用,到現在以觀察者模式(Observer pattern)重新被實踐,接下來我們來看看要怎麼的使用 MutationObserver 吧!

MutationObserver 的使用方式

1. 建構 MutationObserver 實體

MutationObserver 作為一建構子,需要實體化,並傳入一個回傳函式,其函式接受一組包含 mutationRecord (MDN) 的陣列,根據 mutationRecord 的內容,我們可以取得 DOM 已完成的更動,諸如 DOM 的屬性(attributes)或子節點(childList)的變化,以及變化前後的值,都可以從 mutationRecord取得。

const observer = new MutationObserver((mutationRecords) => {
    mutationRecords.forEach((mutationRecord) => {
    switch (mutationRecord.type) {
            case 'attributes':
                console.log('元素屬性變化');
                break;
            case 'characterData':
                console.log('文字內容屬性變化');
                break;
            case 'childList':
                console.log('子節點變化';
                break;
    }
  })
};

不知道大家會不會疑惑,為什麼觀察到的變化會是一個陣列呢?那是因為 MutationObserver 的行為是非同步的,待目前所有對 DOM 的變更完成後,才能觸發,藉此避免大量的效能浪費。

2. MutationObserver 實體執行 observe(element, options)

options 有以下的屬性可以使用:

1. 偵測更動的類型(必須擇一開啟):

  • attributes (Boolean) 偵測屬性的更動
  • childList (Boolean)偵測子節點的更動
  • charactorData (Boolean)偵測字元內容更動

2. 偵測更動的範圍:

  • subtree(Boolean)對其節點下所有延伸的節點進行觀察
  • attributeFilter (String[])過濾偵測更動的屬性,需要 attributes 設為 ture

3. 捕抓更動前的狀態:

  • attributeOldValue (Boolean)屬性更動前的值
  • characterDataOldValue (Boolean)字元內容更動前的值
observer.observe(document.body, {
  attributes: true, // 偵測屬性的更動
    subtree: true, // 對其節點下所有延伸的節點進行觀察
})

以下是簡單的 playground,可以勾選不同的 option 測試看看:

MutationObserver 使用時機與實際應用

講解完使用方法,那究竟在什麼實際情況下,會使用上 MutationObserver 呢?而我目前使用到 MutationObserver 的時機,主要是以下兩個:

1. 製作第三方工具

假如你需要製作第三方的工具,例如製作 Chrome extension 時,需要偵測畫面有新元素的加入時,為加入內容進行擴充,這時候便是 MutationObserver 發揮所長的時候。

這邊舉個簡單的例子,假如我想為 YouTube 首頁每一個影片縮圖加入提示,假如我在載入頁面後馬上執行,雖然目前的內容有加入成功,但延遲載入加載的內容則無法套用 (為演示方便直接 console 執行):

使用 mutationObserver 前:

只有目前顯示的影片有效,延遲載入新增的內容不會被影響

這時候便可以使用 MutationObserver 偵測頁面的內容是否有變動,並在變動時對新增的內容(這次例子是影片縮圖)進行操作:

使用 mutationObserver 後:

延遲載入的影片也有綠色點點了

範例程式碼:

const observer = new MutationObserver((mutations) => {
  const imageAdded = mutations.some((mutation) => {
    return mutation.type === 'childList' && mutation.addedNodes.length > 0;
  });

  if (imageAdded) {
    const imageBlocks = document.querySelectorAll('yt-image');

    observer.disconnect();

    imageBlocks.forEach((imageBlock) => {
      if (!imageBlock.dataset.added) {
        imageBlock.after(createLabel());
        imageBlock.dataset.added = true;
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  }
});

function createLabel() {
  const label = document.createElement('div');
  const styles = {
    position: 'absolute',
    left: '5px',
    top: '5px',
    backgroundColor: '#22ff44',
    width: '20px',
    height: '20px',
    borderRadius: '20px',
  };
  Object.assign(label.style, styles);

  return label;
}

observer.observe(document.body, {
  childList: true,
  subtree: true,
});

2. 內容編輯器

前端的大家應該對元素的 contenteditable 屬性並不陌生,對於取得因 contenteditable 而變更的內容,通常會使用 input 事件 ,但如果要以 contenteditable 為基礎製作內容編輯器時,其元素內會有多層的元素結構,當要比對更新的內容時會非常不便;這時也可以考慮使用 MutationObserver 進行實作,便可以快速找到是哪一個元素的內容觸發更新:

MutationObserver 的注意事項

作為觀察者模式 API,MutationObserver 給予我們監聽元素內容變動的機制,便於對第三方內容進行後處理;在團隊的應用上,也可以做出如 CustomEvent 有助減少程式藕合的開發體驗。

另外,對於進行觀察的元素,應該儘可能把範圍限縮,如上面第一個實作的範例,就可以從 document.body 限縮至右側的主內容 #content,對於執行 oberver() 時,也可以限縮觀察的選項,減少不必要的 callback,以利節省效能。

而在瀏覽器相容性方面,連被停止支援(放逐)的 IE 11 也適用,所以可以放心使用:

mutationObserver

經過以上介紹,希望能讓大家能多認識 MutationObserver 這個 API ,並活用於前端開發上喔。

參考

  • MDN MutationObserver
  • https://stackoverflow.com/questions/14564617/when-are-mutationobserver-callbacks-fired

上一篇

Gatsby 部落格更新記錄 1.0

下一篇

Raycast — Mac 提升工作效率的啟動工具


Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發

Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發


共29篇文章
文章分類
  • 前端開發14
  • JavaScript7
  • 那些前端開發應該要知道的小事5
  • Gatsby.js3
  • ChromeExtension2
  • NetlifyCMS2
  • 生產力工具2
  • 關於我1
  • 非Coding1
  • Svelte1
  • raycast1
  • toolRecommend1
  • productivity1
  • MutationObserver1
  • observer pattern1
  • 開發效率1
  • will-change1
  • GitHubCopilot1
  • 生產力1
  • ChatGPT1
  • AI1
  • Copilot1
  • WebAssembly1
  • SEO1
  • GoogleSearchConsole1
  • react1
  • applescript1
  • 閱讀1
  • css1
  • grid1
  • micro-frontend1

Build with GatsbyJS and React 18.2.0. Hosted on Netlify.

The original code is open source and available at calpa/gatsby-starter-calpa-blog

Copyright ©AlexIan's blog 2025.