10/22/2018

偵測網頁 DOM 新增節點(元素)最方便的方法﹍利用 CSS 動畫技巧

偵測網頁 DOM 新增節點(元素)最方便的方法﹍利用 CSS 動畫技巧

A+
最近這個案例比較特殊,幫客戶改付費範本次數一多,某些比較貴的範本,作者為了不想讓別人看懂程式碼,雖然 JS 沒有使用混淆 (obfuscate),但是壓縮、重組得不成人形,導致完全無法去閱讀解析這些 JS 程式碼。很多動態生成的區塊,最終只能等 DOM 完整成形後,再來寫 JS 動態修改 DOM 指定區塊,調整為客製的需求效果。

但有時麻煩的是,動態產生的 DOM 新增元素,用 JS 不容易監控,除了不知道何時會新增目標節點,之後的 callback 函數也不知何時能執行。用 window onload 是一種解法,但有時會遇到網路延遲的問題,導致 window onload 遲遲無法觸發;另外就是也有可能,動態新增的元素,在 window onload 之後才會出現。

以往 JS 可利用監控 Mutation events 事件來偵測 DOM 變動,也曾寫過一篇利用其中一個事件「DOMSubtreeModified」來監控元素是否產生子節點,但可惜的是,Mutation 事件現在已經被網頁規範淘汰,可參考「Mutation events」,可能是這個方法消耗太多瀏覽器效能的緣故。

新的瀏覽器版本將來一定不支援 Mutation 的情況下,要如何解決這個難題,請見本篇的整理。

(圖片出處: pixabay.com)


一、新的監控方法 MutationObserver


揮棄 Mutation 後,新的網頁規範提供了「MutationObserver」 這個新的 API。需要中文教學的話,可參考這篇「JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化」。

我在這個討論串「jquery detecting div of certain class has been added to DOM」找到了範例程式碼:

var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation)
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
// element added to DOM
var hasClass = [].some.call(mutation.addedNodes, function(el) {
return el.classList.contains('MyClass')
});
if (hasClass) {
// element has class `MyClass`
console.log('element ".MyClass" added');
}
}
});
});

var config = {
attributes: true,
childList: true,
characterData: true
};

observer.observe(document.body, config);

這段程式碼大概的意思是這樣:

  • 監控 document.body (也就是整個 DOM) 的變化
  • 包含元素屬性、是否新增子元素等變化
  • 偵測到任何變化時,對所有變化 loop 迴圈
  • 一個個檢查變化,如果有新增子元素時,檢查子元素的 class 是否為標的

程式碼是看得懂,但是覺得瀏覽器的耗費的 CPU 運作很驚人,因為整個網頁 DOM 載入過程不斷生成節點,而每產生一個節點就會 loop 一次 mutations 的工作!

雖然廢棄 Mutation events 是因為效能的關係,但這個新的 MutationObserver,從範例程式碼來看,實在不覺得對網頁效能有多大幫助,所以我不太敢用...



二、CSS animation 動畫技巧


後來找到這篇「I Want a DAMNodeInserted Event!」,作者真的神乎其技,竟然想到可以利用 CSS3 的動畫語法 animation,來偵測 DOM 新增元素。

其原理很簡單,可參考這篇中文說明「跟蹤DOM的改變」→「CSS動畫」,這裡就不贅述了。

如此一來,使用的 CSS/JS 語法都很短、不複雜,也不需耗費瀏覽器多大的效能來監控 DOM,我認為是最佳解。

以下提供的範例程式碼,針對以上中文說明的程式碼進行修改。



三、範例程式碼


<style>
@keyframes nodeInserted {
from { opacity: 0.99; }
to { opacity: 1; }
}
.add_node {
animation-duration: 0.001s;
animation-name: nodeInserted;
}
</style>

<script src='//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'></script>

<a id="WFU_test" href="javascript:">點此新增監控元素</a>

<script>
function addNodeListener(event) {
if (event.animationName === "nodeInserted") {
// 找到目標元素
var target = event.target;
// 在目標元素動態產生 HTML 內容
$(target).html("這裡是新增元素的 HTML 內容");
alert("發現目標元素產生");
}
}

// 跨瀏覽器監控目標元素是否產生
document.addEventListener("animationstart", addNodeListener, false); // FF
document.addEventListener("MSAnimationStart", addNodeListener, false); // IE
document.addEventListener("webkitAnimationStart", addNodeListener, false); // Chrome

// 點擊 WFU_test
$("#WFU_test").click(function () {
// 產生一個目標元素 但無 HTML 內容
$(this).after("<div class='add_node'></div>");
});
</script>

說明一下以上程式碼在做什麼:

  • 監控 DOM 產生 class 為 add_node 的元素
  • 為 .add_node 設定動畫,透明度從 0.99 變成 1,持續時間只有 0.01 秒,所以肉眼看不到
  • 動畫名稱設為 nodeInserted
  • 監控動畫 nodeInserted 是否執行,當執行的時候呼叫 addNodeListener 函數
  • 函數執行時,找到目標元素 .add_node,動態加入 HTML 內容,並 alert 訊息
  • 點擊 #WFU_test 可動態產生 .add_node,並觸發動畫,自動呼叫 addNodeListener 函數,完成後續動作

以上邏輯非常簡單明瞭,比 MutationObserver 易學易懂多了,可點擊下面這個按鈕即可看到效果:




更多 相關文章:
如這篇文章對你有幫助,歡迎「分享」或給我個讚:

沒有留言:

張貼留言注意事項:

◎ 勾選「通知我」可收到後續回覆的mail!
◎ 請在相關文章留言,與文章無關的主題請至「Blogger 中文論壇」。
◎ 提問若無法提供足夠的資訊供判斷,可能會被無視。建議先參考這篇「Blogger 提問技巧及注意事項」。
◎ CSS 相關問題非免費諮詢,建議使用「Chrome 開發人員工具」尋找答案。
◎ 手機版相關問題請參考「Blogger 行動版範本的特質」→「三、行動版範本不一定能執行網頁版工具」。
◎ 非官方範本問題、或貴站為商業網站,請參考「Blogger 免費諮詢 + 付費諮詢
◎ 若留言要輸入語法,"<"、">"這兩個符號請用其他符號代替,否則語法會消失!
◎ 為了過濾垃圾留言,所有留言不會即時發佈,請稍待片刻。
◎ 本站「已關閉自刪留言功能」。

TOP