前置
正如你所见,我现在用的这个博客皮肤,在没优化之前帧率会降到个位数. 现在与之相比,是不是好很多呀? 下面将从滚动 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 || right - left
}
}
如果你对 Element.getBoundingClientRect()
没有了解, 如果有兴趣, 我在这里放了一个链接-MDN, 你可以跳转以学习它.
百分比的指示器
例如右下角带百分比的指示器,通过监听滚动条位置转化成百分比,同时改变元素高度, 以控制动画的高度. 这里就犯了大忌了, 不断改变元素高度, 会导致不断重绘. 这部分使用 requestAnimationFrame
来优化, 虽然帧数提升明显, 这样仍然是极不好的做法, 不要在监听滚动的事件中修改样式! 找了很久没找到能够不改变高度就能实现这个效果的方式, 就给这个皮肤添加了一个可以选择简易指示器的选项.
back2top: {
enable: true,
type: "complex", // 可选 'simple' 不使用动画效果
right: ""
},
这个指示器的配置为什么叫 back2top
? 这是因为如果你将鼠标放上去,它会显示一个箭头,点击可以回到顶部,它其实是一个返回顶部的按钮.
如果你对 window.requestAnimationFrame
没有了解, 如果有兴趣, 我在这里放了一个链接-MDN, 你可以跳转以学习它.
最后
博客还有其他地方类似优化, 思路重复就不一一列举了.
其实完全可以删去很多滚动监听事件, 这样也好, 能够稍微锻炼一下自己, 稍微增加这方面经验.
文章如有错误不足,敬请指出,谢谢!