如何在 Vue 觀察 DOM: MutationObserver
我腦袋枯竭,不想寫文章……最近怎麼了?
問題
總之,當你想觀察 Vue 內部的 DOM 屬性時,你可能會這麼寫:
<template>
<main>
<nav ref="foo">
<!-- DOMs -->
</nav>
</main>
</template>
<script>
export default {
watch: {
"$refs.foo"(newval) {
// Do something...
},
},
}
</script>
**這麼做會失敗。**因為 Vue 的響應式原理是 Object.defineProperty,沒有實做到 DOM 的層面。
我覺得我有點北七。
解答
那麼該怎麼做?答案是使用MutationObserver──這個 API 可以追蹤特定的 DOM 更動,並提供該 DOM 具體的資訊。
你需要丟幾個東西給 MutationObserver
:
- 目標 DOM。
- 監聽時的動作。
- 設定。
所以我們試試看這樣:
<template>
<main>
<nav ref="foo">
<img src="https://example.com/bar.png" />
<div ref="baz">{{ text }}</div>
</nav>
</main>
</template>
<script>
export default {
data() {
return { observer: null, text: "" };
},
methods: {
change_it() { this.flag = !this.flag; },
active_observer() {
const callback = (mutationsList, observer) => {
// 在這裡,mutationsList 會傳一個被變動的 DOM 狀態陣列
// 而 observer 則回傳被呼叫的 MutationObserver 本身
// 詳細參見: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver
};
const dom = this.$refs.foo;
const cfg = { attributes: true, childList: true, subtree: true };
this.observer = new MutationObserver( callback );
this.observer.observe( dom, cfg );
}
},
mounted() {
this.active_observer();
},
beforeDestroy() {
if( this.observer ) {
this.observer.disconnect();
}
},
};
</script>
記住,觀察 DOM 只能放在 mounted
裡面。
這樣做以後,這個組件內的 DOM 變化,被綁定的 observer
狀態,就可以告訴我們,有什麼 DOM 變動了:像是可以用 getBoundingClientRect API 來監測 DOM 的位置。
無法解決的問題
這樣就大功告成了,但有一些問題……
首先,如果某個 DOM 本身尚未存在,那個 DOM 將只會是 null
,而 MutationObserver
愛莫能助。
所以如果把範例的 this.active_observer();
放在剛建立組件的 created
而非已經形成 DOM 實體的 mounted
裡面,就會出現以下錯誤:
TypeError: MutationObserver.observe: Argument 1 is not an object.
所以你必須確保在開始呼叫 MutationObserver 前,該 DOM 就一定存在。
另一個問題要回頭看看範例的 HTML:
<template>
<main>
<nav ref="foo">
<img ref="bar" src="https://example.com/bar.png" />
<div ref="baz">{{ text }}</div>
</nav>
</main>
</template>
雖然我們可以觀察 ref="baz"
的 DOM,但如果要觀察 ref="bar"
的圖片載入、還有它對 ref="foo"
的影響,那 MutationObserver
就派不上用場了。
這樣要怎麼辦呢?
我會改天說明的。
結語
- 你不能用 Vue 的
watch
instance 來觀察 DOM 屬性的變化,因為 Vue 響應式設計原理並沒有觀察 DOM 屬性。 - 所以,如果要觀察 DOM 屬性,你需要使用
MutationObserver
。 - 要使用
MutationObserver
的話,你需要確保被監聽的 DOM 自始存在;而且MutationObserver
無法觀察圖片載入與否。