1. 程式人生 > >從這個部落格面板邁入前端效能優化一小步

從這個部落格面板邁入前端效能優化一小步

# 前置 正如你所見,我現在用的這個部落格面板,在沒優化之前幀率會降到個位數. 現在與之相比,是不是好很多呀? 下面將從滾動 scroll 優化這一方面展開,主要說一下思路. ![](https://img2020.cnblogs.com/blog/1501373/202003/1501373-20200318101718711-885124719.png) 只在極少情況下會降到 30fps,一般穩定在 55-60fpx. # 頭部導航條 頭部導航條會監聽滾動條上下滾動的方向隨之展開或隱藏.當隱藏時,文章目錄會上移一小段距離並固定;反之,會回到原來的位置.這裡的頭部導航條使用了 css3 `transform` 屬性, 有時會調動 GPU 來加速, 所以導航條優化主要在於對監聽滾動條事件的處理. **不好的做法** 頭部導航條和文章目錄各監聽一個事件,分別寫在一個 func 中. 這樣會增加一個事件消耗,且程式碼會有冗餘. **優化** 仔細分析, 文章目錄是隨頭部導航條的變化而變化的.完全可以做一次事件監聽完成這兩件事,例如下面這樣: ```js $(window).scroll(function() { if (condition) { // 顯示導航條 // 目錄下移 } else { // 隱藏導航條 // 目錄上移 } }) ``` 因為程式碼量比較大, 當時寫的時候就沒考慮合併, 主要是先把功能做出來. 有了經驗,以後再遇到類似的情況,就會考慮他們之間的聯絡了, 這裡的重點就是找到這兩者之間的聯絡. 但是這樣就完了嗎 ? 有些經驗的, 可能想到防抖(debounce), 但仔細想想防抖真的夠好嗎? 如果加上防抖, 只會降低在一段時間內事件觸發的頻率.這樣能減少很大的效能開銷,一般遇到監聽 scroll 或者 resize 等事件首先都會這樣想吧. 仔細觀察,就可以發現: 如果一直向上或向下滾動,導航條和文章目錄都會保持一個狀態, 只有逆向滾動時,它們才會一起發生一次狀態改變. 這裡有更好的做法: ```js function scrollFunc() { let scrollDirection if (!scrollAction) { scrollAction = window.pageYOffset } let diff = scrollAction - window.pageYOffset if (diff < 0) { scrollDirection = "down" } else if (diff > 0) { scrollDirection = "up" } scrollAction = window.pageYOffset return scrollDirection } let scrollAction, originalDir $(window).scroll(function() { let direction = scrollFunc() if (direction && originalDir != direction) { if (direction == "down") { // 顯示導航條 // 目錄下移 } else { // 隱藏導航條 // 目錄上移 } originalDir = direction } }) ``` 上面這段程式碼,很容易明白.概括一下: 相當於增加一個做**更少計算**的中間層,只有符合條件時才會觸發真正需要執行的操作. 甚至想讓給這個所謂的**中間層**加上防抖也是可以的.這樣和原來的對比更加明顯了. # 文章目錄活躍標題樣式 監聽滾動條滾動,如果這個文章標題超出了頂部, 即認為當前標題下的內容活躍(你正在瀏覽這部分),就會給當前目錄中的標題新增活躍樣式. 很顯然,這裡涉及遍歷操作,所以計算量較大.這裡可以使用防抖來優化,但是我使用了節流.使用節流能保證你較快速滾動頁面時,依然能觸發指定頻次的 func,以顯示目錄活躍狀態. 這樣看起來就更平滑.使用防抖則不能,會等你停止滑動瞬間移動給最後一個活躍的標題新增樣式,這樣有明顯的頓感.當然使用防抖也是可以的.很顯然,這裡使用節流仍然會增加部分開銷. 另外,有更好的方法實現文章目錄,比如使用 canvas 來繪製.我寫的程式碼有些糟,可以另做一番優化. 另外補充一個小知識, 如何判斷文章標題是否超出了頂部呢 ? 我是這樣實現的: ```js getClientRect(element) { const {top, bottom, left, right, height, width} = element.getBoundingClientRect() return { top, bottom, left, right, height: height || bottom - top, width: width || right - left } } ``` 如果你對 `Element.getBoundingClientRect()` 沒有了解, 如果有興趣, 我在這裡放了一個[連結-MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect), 你可以跳轉以學習它. # 百分比的指示器 例如右下角帶百分比的指示器,通過監聽滾動條位置轉化成百分比,同時改變元素高度, 以控制動畫的高度. 這裡就犯了大忌了, 不斷改變元素高度, 會導致不斷重繪. 這部分使用 `requestAnimationFrame` 來優化, 雖然幀數提升明顯, 這樣仍然是極不好的做法, 不要在監聽滾動的事件中修改樣式! 找了很久沒找到能夠不改變高度就能實現這個效果的方式, 就給這個面板添加了一個可以選擇簡易指示器的選項. ```js back2top: { enable: true, type: "complex", // 可選 'simple' 不使用動畫效果 right: "" }, ``` 這個指示器的配置為什麼叫 `back2top` ? 這是因為如果你將滑鼠放上去,它會顯示一個箭頭,點選可以回到頂部,它其實是一個返回頂部的按鈕. 如果你對 `window.requestAnimationFrame` 沒有了解, 如果有興趣, 我在這裡放了一個[連結-MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame), 你可以跳轉以學習它. # 最後 另外, 沒優化之前在firefox上就很流暢 <( ̄▽ ̄)/ 部落格還有其他地方的滾動監聽, 思路重複就不一一列舉了. 其實完全可以刪去很多滾動監聽事件, 這樣也好, 能夠稍微鍛鍊一下自己, 稍微增加這方面經驗. 文章有錯誤不足,敬請指出,謝謝!