从这个博客皮肤迈入前端性能优化一小步

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, 你可以跳转以学习它.

最后

博客还有其他地方类似优化, 思路重复就不一一列举了.

其实完全可以删去很多滚动监听事件, 这样也好, 能够稍微锻炼一下自己, 稍微增加这方面经验.

文章如有错误不足,敬请指出,谢谢!

posted @ 2020-03-18 10:19  DIVMonster  阅读(...)  评论(...编辑  收藏