最近這個案例比較特殊,幫客戶改付費範本次數一多,某些比較貴的範本,作者為了不想讓別人看懂程式碼,雖然 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)
揮棄 Mutation 後,新的網頁規範提供了「MutationObserver」 這個新的 API。需要中文教學的話,可參考這篇「JavaScript 是如何運作的:用 MutationObserver 追蹤 DOM 的變化」。
我在這個討論串「jquery detecting div of certain class has been added to DOM」找到了範例程式碼:
這段程式碼大概的意思是這樣:
程式碼是看得懂,但是覺得瀏覽器的耗費的 CPU 運作很驚人,因為整個網頁 DOM 載入過程不斷生成節點,而每產生一個節點就會 loop 一次 mutations 的工作!
雖然廢棄 Mutation events 是因為效能的關係,但這個新的 MutationObserver,從範例程式碼來看,實在不覺得對網頁效能有多大幫助,所以我不太敢用...
後來找到這篇「I Want a DAMNodeInserted Event!」,作者真的神乎其技,竟然想到可以利用 CSS3 的動畫語法 animation,來偵測 DOM 新增元素。
其原理很簡單,可參考這篇中文說明「跟蹤DOM的改變」→「CSS動畫」,這裡就不贅述了。
如此一來,使用的 CSS/JS 語法都很短、不複雜,也不需耗費瀏覽器多大的效能來監控 DOM,我認為是最佳解。
以下提供的範例程式碼,針對以上中文說明的程式碼進行修改。
說明一下以上程式碼在做什麼:
以上邏輯非常簡單明瞭,比 MutationObserver 易學易懂多了,可點擊下面這個按鈕即可看到效果:
但有時麻煩的是,動態產生的 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 易學易懂多了,可點擊下面這個按鈕即可看到效果:
更多 相關文章: