記一次前端效能優化的案例

呂大豹發表於2017-11-02

前兩天遇到一個前端效能相關的bug,感覺還挺典型的,整理了一下解決過程和思路,寫下來分享給大家。

場景是這樣的,有一個答題的介面,可以播放音訊、填空、提交答案,介面是長這個樣子的:

看起來還挺簡單吧,但是我們在手機上跑的時候,卻遇到了以下問題:

1. 填完空後,提交按鈕會由灰色變為藍色(可提交狀態),但是播放完音訊後,卻無法變藍

2. 頁面較長時,一邊播音訊一邊滾動頁面,會出現頁面閃爍(短時白屏)

我的第一反應就是:出渲染bug了。因為在一些低端手機上,經常會遇到動態修改頁面,渲染沒有及時生效,出現花屏或者白屏的情況。

而修改這類bug並沒有什麼好方法,唯一管用的就是強制瀏覽器再重繪一次。常用的技術手段比如設定style.visibility="visible",或者是更新一下className。有時候這種“輕度重繪”起不到作用的話,還會修改背景色啦,或者先display:none然後在display:block,目的都是強制觸發瀏覽器的reflow或者repaint,期望它能給渲染正常。

因此我不假思索使出老手段。但是在嘗試各種強制重繪後,並沒有解決問題1。這我也是能想通的,畢竟是不穩定的hack手段,不生效也是情有可原。

有人會問,是不是邏輯寫的有問題啊?經我排查其實並沒有邏輯問題,我們是用vue的:class繫結做的樣式更新,此時該狀態的變數已經更新了,而且class的屬性值也更新了,只是視覺上沒看到更新而已,還是渲染的問題沒錯。

此時我有一個膽大的想法:不會是vue的bug吧!猜測如下:vue內部更新class值的時候是不是有什麼機制,導致瀏覽器在某些特殊情況下忽略了這次渲染。其實我並不願意這樣想,畢竟vue已經是一個很穩定的框架了,不至於有這麼低階的問題吧,就算真有,應該早就暴漏出來了呀。

但是事實就擺在我眼前,而我還必須解決這個bug,所以還得想辦法。

“要不,這裡就不用vue繫結了。”

不用vue繫結,自己手動操作更新className。這實在是下下策,我甚至感到有點羞恥。因為我是有程式碼潔癖的,用vue本身就是為了不手動操作DOM,而現在,卻要在用vue一氣呵成的程式碼中插入一段手動操作DOM的程式碼。簡直是一顆老鼠屎,破壞了整個程式碼的完美度。

上線要緊啊,忍了。於是我把提交按鈕這裡,改為了手動更新className。問題解決了,提交按鈕乖乖變藍,播放音訊的動作不會影響到它了。大家誇我快速解決了問題,而我的良心隱隱作痛。

故事並沒有結束,其實,重點才剛剛開始。直到遇到問題2,才讓我開始重新審視這個問題。問題2是這樣的:在頁面高度特別長的時候,會有滾動頁面的操作。當正在播放音訊的時候去滾動頁面,大概在播完音訊的那個瞬間(頁面還在滾),頁面會發生一次嚴重的抖動,直接白屏一下,然後頁面重新正常顯示。

這下好了,沒法用重繪hack,也沒法再用手動操作DOM的噁心方法了。此時,不得不拿出除錯利器了:chrome的devtool。在Rendering工具中,勾選Paint Flashing,它能夠高亮頁面被重繪的區域。

有發現了!在音訊播放完畢的時候,提交按鈕那塊區域竟然發生了一次重繪。這怎麼回事呢?它倆隔著老遠,而且並沒有父子關係,況且提交按鈕還是絕對定位放在下面的。我想不到什麼原因能讓下邊的提交按鈕發生重繪。

重頭來了,這時我開啟了Layers工具,看到的景象讓我大吃一驚。看下面的動圖吧:

音訊播放元件那裡的graphics layer竟然如此之亂,在開始播放的時候,發生了較多的層提升和層合併。更為奇葩的時,下方的提交按鈕區域也跟著發生了層提升。如果你細細觀察,還能看到右上角的那片綠葉子也發生了層提升,你這傢伙跟著起鬨什麼啊。。。

這下問題的癥結就比較清楚了,多餘的層變動,導致意外的重繪。恰逢頁面正在滾動,一下遇到了渲染瓶頸,就出現了閃爍。那麼,是什麼原因導致的層變動呢?

經過審查程式碼,查到了問題所在,總結如下:

音訊元件的佈局方式存在問題,左側旋轉的圓盤是右側進度條的子元素,通過絕對定位給定到左側的。並且其高度是大於父元素的,通過父元素的overflow:visible才得以完整顯示。大家知道元素間的遮擋以及裁切都可能會生成新的提升層(graphics layer)。而且左側的圓盤在播放時還會通過transform進行旋轉動畫,transform也會進行層提升,同時瀏覽器還會進行層合併的判斷,將可以合併的合成一個graphics layer。而這個判斷是全域性進行的,也就是說頁面底部的提交按鈕也被計算在內,可能正好命中了某些規則,所以它也被提升為單獨的層。

所以我把音訊元件進行了重新的佈局(減少遮擋與裁切),不讓它產生那麼多的提升層行為。

至於右上角那個綠葉子,我發現他的z-index為100,感覺根本不需要這麼大,改為了2,工作良好,並且不會被提升了。大家知道z-index也是層提升的一個影響因素,很多同學經常隨手就寫一個很大的z-index,生怕自己的元素被別人蓋住。這是一個很不好的習慣,沒準哪天就給命中了層提升的規則,引發重繪了。

經過了上面的修改,再次開啟Layers皮膚,發現此時的層已經很規整了,效果如下:

 

感覺很清爽了有木有。而在此修改之後,問題1的根本原因也定位到了(由於層提升而引發了重繪),並且順勢恢復正常。那段讓我良心隱隱作痛的程式碼也可以刪掉啦!

相關文章