當前位置:
首頁 > 最新 > 高頻dom操作和頁面性能優化探索

高頻dom操作和頁面性能優化探索

一、DOM操作影響頁面性能的核心問題

通過js操作DOM的代價很高,影響頁面性能的主要問題有如下幾點:

訪問和修改DOM元素

修改DOM元素的樣式,導致重繪或重排

通過對DOM元素的事件處理,完成與用戶的交互功能

DOM的修改會導致重繪和重排。

重繪是指一些樣式的修改,元素的位置和大小都沒有改變;

重排是指元素的位置或尺寸發生了變化,瀏覽器需要重新計算渲染樹,而新的渲染樹建立後,瀏覽器會重新繪製受影響的元素。

頁面重繪的速度要比頁面重排的速度快,在頁面交互中要盡量避免頁面的重排操作。瀏覽器不會在js執行的時候更新DOM,而是會把這些DOM操作存放在一個隊列中,在js執行完之後按順序一次性執行完畢,因此在js執行過程中用戶一直在被阻塞。

1.頁面渲染過程

一個頁面更新時,渲染過程大致如下:

JavaScript: 通過js來製作動畫效果或操作DOM實現交互效果

Style: 計算樣式,如果元素的樣式有改變,在這一步重新計算樣式,並匹配到對應的DOM上

Layout: 根據上一步的DOM樣式規則,重新進行布局(重排)

Paint: 在多個渲染層上,對新的布局重新繪製(重繪)

Composite: 將繪製好的多個渲染層合併,顯示到屏幕上

在網頁生成的時候,至少會進行一次布局和渲染,在後面用戶的操作時,不斷的進行重繪或重排,因此如果在js中存在很多DOM操作,就會不斷地出發重繪或重排,影響頁面性能。

2.DOM操作對頁面性能的影響

如前面所說,DOM操作影響頁面性能的核心問題主要在於DOM操作導致了頁面的重繪或重排,為了減少由於重繪和重排對網頁性能的影響,我們要知道都有哪些操作會導致頁面的重繪或者重排。

2.1 導致頁面重排的一些操作:

內容改變

文本改變或圖片尺寸改變

DOM元素的幾何屬性的變化

例如改變DOM元素的寬高值時,原渲染樹中的相關節點會失效,瀏覽器會根據變化後的DOM重新排建渲染樹中的相關節點。如果父節點的幾何屬性變化時,還會使其子節點及後續兄弟節點重新計算位置等,造成一系列的重排。

DOM樹的結構變化

添加DOM節點、修改DOM節點位置及刪除某個節點都是對DOM樹的更改,會造成頁面的重排。瀏覽器布局是從上到下的過程,修改當前元素不會對其前邊已經遍歷過的元素造成影響,但是如果在所有的節點前添加一個新的元素,則後續的所有元素都要進行重排。

獲取某些屬性

除了渲染樹的直接變化,當獲取一些屬性值時,瀏覽器為取得正確的值也會發生重排,這些屬性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle()。

瀏覽器窗口尺寸改變

窗口尺寸的改變會影響整個網頁內元素的尺寸的改變,即DOM元素的集合屬性變化,因此會造成重排。

2.2 導致頁面重繪的操作

應用新的樣式或者修改任何影響元素外觀的屬性

只改變了元素的樣式,並未改變元素大小、位置,此時只涉及到重繪操作。

重排一定會導致重繪

一個元素的重排一定會影響到渲染樹的變化,因此也一定會涉及到頁面的重繪。

二、高頻操作DOM會導致的問題

接下來會分享一下在平時項目中由於高頻操作DOM影響網頁性能的問題。

1. 抽獎項目的高頻操作DOM問題1.1 存在的問題

在最近做的抽獎項目中,就遇到了這樣的由於高頻操作DOM,導致頁面性能變差的問題。在經歷幾輪抽獎後,文字滾動速度越來越慢,肉眼能感受到與第一次抽獎時文字滾動速度的明顯差別,如持續時間過長或輪次過多,還會造成瀏覽器假死現象。

1.2 問題分析

下圖為抽獎時文字滾動過程中的timeline記錄。

timeline分析:

FPS:最上面一欄為綠色柱形為幀率(FPS),頂點值為60fps,上方紅色方塊表示長幀,這些長幀被Chrome稱為jank(卡頓)。

CPU:第二欄為CPU,藍色表示loading(網路通信和HTML解析),黃色表示scripting(js執行時間),紫色表示rendering(樣式計算和布局,即重排), 綠色為painting(即重繪)。

更多timeline使用方法可參考:如何使用Chrome Timeline 工具(譯)(http://www.jianshu.com/p/4da0f0bda768)

由上圖可以看出,在文字滾動過程中紅色方塊出現頻繁,頁面中存在的卡頓過多。幀率的值越低,人眼感受到的效果越差。

參考文章:腦洞大開:為啥幀率達到 60 fps 就流暢?(http://www.jianshu.com/p/71cba1711de0)。

接下來選擇一段長幀區域放大來看

在這段區域內最大一幀達到了49.7ms,幀率只有20fps,接下來看看這一幀里是什麼因素耗時過長

由上圖可以看出,耗時最大的在scripting,js的執行時間達到了44.9ms,佔總時間的93.2%,因為主要靠js計算控制DOM的顯示內容,所以js運行時間過長。

選取一段FPS值很低的部分查看造成這段值低的原因

由下圖可看出主要為dom.html中的js執行佔用時間。

點進dom.html文件,即可定位到該函數

由此可知,主要是rolling這個函數執行時間過長,對該部分失幀影響較大。而這個函數的主要作用就是實現文字的滾動效果,也可以從代碼中看出,這個函數利用的setTimeout來反覆執行,並且在這個函數中存在著循環以及大量的DOM操作,造成了頁面的失幀等問題。

1.3 優化方案

針對該項目中的問題,採取的解決方法是:

一次性生成全部,並且隱藏這些,隨機生成一組隨機數數組,只有index與數組裡面的隨機數相等時,才顯示該位置的,雖然也會觸發重排和重繪,但是性能要遠遠高於直接操作DOM的添加和刪除。

用requestAnimationFrame取代setTimeout不斷生成隨機數。

requestAnimationFrame與setTimeout和setInterval類似,都是通過遞歸調用同一個方法不斷更新頁面。

:在特定的時間後執行函數,而且只執行一次,如果在特定時間前想取消執行函數,可以用clearTimeout立即取消執行。但是並不是每次執行setTimeout都會在特定的時間後執行,頁面載入後js會按照主線程中的順序按序執行那個,如果在延遲時間內主線程不空閑,setTimeout裡面的函數是不會執行的,它會延遲到主線程空閑時才執行。

:在特定的時間間隔內重複執行函數,除非主動清除它,不然會一直執行下去,清除函數可以使用clearInterval。setInterval也會等到主線程空閑了再執行,但是setInterval去排隊時,如果發現自己還在隊列中未執行,就會被drop掉,所以可能會造成某段時間的函數未被執行。

:它不需要設置時間間隔,它會在瀏覽器每次刷新之前執行回調函數的任務。這樣我們動畫的更新就能和瀏覽器的刷新頻率保持一致。requestAnimationFrame在運行時,瀏覽器會自動優化方法的調用,並且如果頁面不是激活狀態下的話,動畫會自動暫停,有效節省了CPU開銷。

在採用上面的方法進行優化後,在經歷多輪抽獎後,文字滾動速度依舊正常,網頁性能良好,不會出現文字滾動速度越來越慢,最後導致瀏覽器假死的現象。

1.4 優化前後FPS對比

優化前文字滾動時的timeline

優化後文字滾動時的timeline

優化前的代碼對DOM操作很頻繁,因此FPS值普遍偏低,而優化後可以看出紅色方塊明顯減少,FPS值一直處於高值。

1.5 優化前後CPU佔用對比

優化前文字滾動時的timeline

優化後文字滾動時的timeline

優化前js的CPU佔用率較高,而優化後佔用CPU的主要為渲染時間,因為優化後的代碼只是控制了節點的顯示和隱藏,所以在js上消耗較少,在渲染上消耗較大。

2.吸頂導航條相關及scroll滾動優化2.1 存在的問題

吸頂導航條要求當頁面滾動到某個區域時,對應該區域的導航條在設置的顯示範圍內保持吸頂顯示。涉及到的操作:

監聽頁面的scroll事件

在頁面滾動時進行計算和DOM操作

計算:計算當前所在位置是否為對應導航條的顯示範圍

DOM操作:顯示在範圍內的導航條並且隱藏其他導航條

由於scroll事件被觸發的頻率高、間隔近,如果此時進行DOM操作或計算並且這些DOM操作和計算無法在下一次scroll事件發生前完成,就會造成掉幀、頁面卡頓,影響用戶體驗。

2.2 優化方案

針對該項目中的問題,採取的解決方法是:

盡量控制DOM的顯示或隱藏,而不是刪除或添加:

頁面載入時根據當前頁面中吸頂導航的數量複製對應的DOM,並且隱藏這些導航。當頁面滾動到指定區域後,顯示對應的導航。

一次性操作DOM:

將複製的DOM存儲到數組中,將該數組append到對應的父節點下,而不是根據複製得到DOM的數量依次循環插入到父節點下。

多做緩存:

如果某個節點將在後續進行多次操作,可以將該節點利用變數存儲起來,而不是每次進行操作時都去查找一遍該節點。

使用 requestAnimationFrame優化頁面滾動

對於scroll的滾動優化還可以採用防抖(Debouncing)和節流(Throttling)的方式,但是防抖和節流的方式還是要藉助於setTimeout,因此和requestAnimationFrame相比,還是requestAnimationFrame實現效果好一些。

參考文章:高性能滾動 scroll 及頁面渲染優化(http://web.jobbole.com/86158/)

三、針對操作DOM的性能優化方法總結

為了減少DOM操作對頁面性能產生的影響,在實現頁面的交互效果時一定要注意一下幾點:

1.減少在循環內進行DOM操作,在循環外部進行DOM緩存

兩個函數的執行時間對比:

優化前的代碼中,每進行一次循環,都會讀取一次div的innerHtml屬性,並且對這個屬性進行了重新賦值,即每循環一次就會操作兩次DOM,因此執行時間很長,頁面性能差。

在優化後的代碼中,將要更新的DOM內容進行緩存,在循環時只操作字元串,循環結束後字元串的值寫入到div中,只進行了一次查找innerHtml屬性和一次對該屬性重新賦值的操作,因此同樣的循環次數先,優化後的方法執行時間遠遠少於優化前。

2.只控制DOM節點的顯示或隱藏,而不是直接去改變DOM結構

上面代碼的優化原理即先生成所有DOM節點,但是所有節點均不顯示出來,利用vue.js中的v-show,根據計算的隨機數來控制顯示某個,來達到文字滾動效果。

對比結果可查看2.4

3.操作DOM前,先把DOM節點刪除或隱藏

display屬性值為none的元素不在渲染樹中,因此對隱藏的元素操作不會引發其他元素的重排。如果要對一個元素進行多次DOM操作,可以先將其隱藏,操作完成後再顯示。這樣只在隱藏和顯示時觸發2次重排,而不會是在每次進行操作時都出發一次重排。

頁面rendering時間對比:

下圖為同樣的循環次數下未隱藏節點直接進行DOM操作的rendering時間(圖一)和隱藏節點再進行DOM操作的rendering時間(圖二)

由對比圖可以看出,總時間、js執行時間以及rendering時間都明顯減少,並且避免了painting以及其他的一些操作。

4. 最小化重繪和重排

在上面的代碼中,每對element進行一次樣式更改都會影響該元素的集合結構,最糟糕情況下會觸發三次重排。

優化方式:利用js或jquery對該元素的class重新賦值,獲得新的樣式,這樣減少了多次的DOM操作。

到此本文結束,如果對於問題分析存在不正確的地方,還請及時指出,多多交流。


點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 JavaScript 的精彩文章:

ajax請求二進位流進行處理
使用CSS完成元素居中的七種方法
JS彈出下載對話框以及實現常見文件類型的下載
如何看待當下的web?
聽說暴風影音殺了一個程序員祭天?

TAG:JavaScript |