今天一樣是接續上篇,分享幾個實際使用 MutationObserver API 的情境。
我們使用 AJSX 時,常常會需要等內容載入後,再執行一些操作。以往多使用 setInterval
或 setTimeout
來輪詢檢查內容是否已載入,現在則可以改用 MutationObserver API 來精準地監控 DOM 的變化,當內容載入完畢後,立刻觸發一個 callback。
<div id="content"></div>
// 模擬 AJAX
setTimeout(() => {
document.getElementById('content').innerHTML = '<p>動態載入的內容</p>';
}, 2000);
// 監控 #content 的變化
const targetNode = document.getElementById('content');
const config = { childList: true };
const callback = (mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('載入完畢:', mutation.addedNodes[0].textContent);
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
我們還能使用 API 動態更新網站畫面,例如當我們在網頁中動態增加或刪除元素時,可以即時更新其他相關的元素。
⬇️ 做了一個過渡的動畫,當我新增資料時,會高亮新增的資料與增加淡入的效果
<style>
#list {
margin-bottom: 20px;
}
#itemCount {
font-weight: bold;
margin-bottom: 20px;
}
p {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
p.show {
opacity: 1;
transform: translateY(0);
}
p.highlight {
background-color: yellow;
}
</style>
<div id="itemCount">目前有 0 筆資料</div>
<div id="list"></div>
<button id="addItem">新增資料</button>
<button id="removeItem">刪除最後一筆資料</button>
⬇️ 監聽 #list
,當他的子節點改變時,MutationObserver 會觸發 updateItemCount
函式,動態更新顯示在 #itemCount
中的數量。
const list = document.getElementById('list');
const addItemButton = document.getElementById('addItem');
const removeItemButton = document.getElementById('removeItem');
const itemCountDiv = document.getElementById('itemCount');
const updateItemCount = () => {
itemCountDiv.textContent = `目前有 ${list.children.length} 筆資料`;
};
addItemButton.addEventListener('click', () => {
const newItem = document.createElement('p');
newItem.textContent = `資料 ${list.children.length + 1}`;
list.appendChild(newItem);
// 動畫效果
requestAnimationFrame(() => {
newItem.classList.add('show');
newItem.classList.add('highlight');
setTimeout(() => {
newItem.classList.remove('highlight');
}, 500);
});
});
removeItemButton.addEventListener('click', () => {
if (list.children.length > 0) {
list.removeChild(list.lastChild);
}
});
// 使用 MutationObserver 監聽 DOM 變更
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
updateItemCount();
}
}
});
observer.observe(list, { childList: true });
⬇️ 最後是這個範例的效果圖
寫到現在,也已經介紹了不少 Web API,大家應該能發現這些 Web API 都很好用,不過要提醒大家,雖然好用,但請不要過度使用。比如 MutationObserver,他很強大沒錯,但過度使用一定會影響網站效能,畢竟是他的本質還是監聽 DOM 元素的變化。
為了避免過度監聽的狀況,我們一定要好好設定監聽的種類,就是上一章節提到的 childList
、attributes
和 subtree
。選對監聽的種類,避免多餘的監聽變化,可以減少不必要的消耗。
如果開發時程很趕為了快速方便,或是初學者不太清楚 DOM 元素之間的關係,很有可能把監聽的範圍設定得很大,同樣會造成無意義的監聽浪費,所以精準的設定監聽範圍,也是很重要的一件事情。
有開就要有關,當我們不再需要監聽 DOM 變化時,可以使用 disconnect
方法斷開這個監聽。
const stopObservingButton = document.getElementById('stopObserving');
stopObservingButton.addEventListener('click', () => {
observer.disconnect();
console.log('監聽已停止');
});
範例程式碼網址:https://mukiwu.github.io/web-api-demo/mutation.html
我還蠻喜歡用 MutationObserver API 取代 setTimeout
或 setInterval
,相較這種要一直輪詢的方式,MutationObserver API 提供了更精準的方式來主動監聽 DOM 的變化,前面給了兩個範例,包括動態新增內容、動態更新 UI,這些都能提升使用者的體驗。
未來 MutationObserver API 的應用應該會愈來愈廣泛,畢竟前端有很大一個技能就是要處理網站上的元素,所以大家可以期待更多這一類的應用。
以上有任何問題,歡迎留言討論。