從這個博客皮膚邁入前端性能優化一小步


GZ/awescnb

前置

正如你所見,我現在用的這個博客皮膚,在沒優化之前幀率會降到個位數. 現在與之相比,是不是好很多呀? 下面將從滾動 scroll 優化這一方面展開,主要說一下思路.

只在極少情況下會降到 30fps,一般穩定在 55-60fps.

頭部導航條

頭部導航條會監聽滾動條上下滾動的方向隨之展開或隱藏.當隱藏時,文章目錄會上移一小段距離並固定;反之,會回到原來的位置.這里的頭部導航條使用了 css3 transform 屬性, 有時會調動 GPU 來加速, 所以導航條優化主要在於對監聽滾動條事件的處理.

不好的做法

頭部導航條和文章目錄各監聽一個事件,分別寫在一個 func 中. 這樣會增加一個事件消耗,且代碼會有冗余.

優化

仔細分析, 文章目錄是隨頭部導航條的變化而變化的.完全可以做一次事件監聽完成這兩件事,例如下面這樣:

$(window).scroll(function() {
  if (condition) {
    // 顯示導航條
    // 目錄下移
  } else {
    // 隱藏導航條
    // 目錄上移
  }
})

因為代碼量比較大, 當時寫的時候就沒考慮合並, 主要是先把功能做出來.
有了經驗,以后再遇到類似的情況,就會考慮他們之間的聯系了, 這里的重點就是找到這兩者之間的聯系.
但是這樣就完了嗎 ? 有些經驗的, 可能想到防抖(debounce), 但仔細想想防抖真的夠好嗎?
如果加上防抖, 只會降低在一段時間內事件觸發的頻率.這樣能減少很大的性能開銷,一般遇到監聽 scroll 或者 resize 等事件首先都會這樣想吧.
仔細觀察,就可以發現: 如果一直向上或向下滾動,導航條和文章目錄都會保持一個狀態, 只有逆向滾動時,它們才會一起發生一次狀態改變.
這里有更好的做法:

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 來繪制.我寫的代碼有些糟,可以另做一番優化.

另外補充一個小知識, 如何判斷文章標題是否超出了頂部呢 ? 我是這樣實現的:

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, 你可以跳轉以學習它.

百分比的指示器

例如右下角帶百分比的指示器,通過監聽滾動條位置轉化成百分比,同時改變元素高度, 以控制動畫的高度. 這里就犯了大忌了, 不斷改變元素高度, 會導致不斷重繪. 這部分使用 requestAnimationFrame 來優化, 雖然幀數提升明顯, 這樣仍然是極不好的做法, 不要在監聽滾動的事件中修改樣式! 找了很久沒找到能夠不改變高度就能實現這個效果的方式, 就給這個皮膚添加了一個可以選擇簡易指示器的選項.

back2top: {
    enable: true,
    type: "complex", // 可選 'simple' 不使用動畫效果
    right: ""
},

這個指示器的配置為什么叫 back2top ? 這是因為如果你將鼠標放上去,它會顯示一個箭頭,點擊可以回到頂部,它其實是一個返回頂部的按鈕.
如果你對 window.requestAnimationFrame 沒有了解, 如果有興趣, 我在這里放了一個鏈接-MDN, 你可以跳轉以學習它.

最后

博客還有其他地方類似優化, 思路重復就不一一列舉了.

其實完全可以刪去很多滾動監聽事件, 這樣也好, 能夠稍微鍛煉一下自己, 稍微增加這方面經驗.

文章如有錯誤不足,敬請指出,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM