对于scroll,resize,mousemove等事件,短时间可能被频繁触发,影响性能。若回调函数中涉及到元素重绘、DOM操作等耗时工作,可能导致在下一次scroll触发前回调未完成,造成掉帧。
解决方案
(一)防抖
让函数延迟触发,若在一定时间内再次触发事件,清除计时器,重新计时
- 适用场景:scroll停止事件,input输入keyup事件等需要在用户操作停止时触发
function debounce(before, after, wait) {
/*option说明: before [事件触发时立即执行的函数];
after [回调函数];
wait [回调触发延迟时间ms]
*/
var timer
return function() {
before && before();
timer && clearTimeout(timer);
timer = setTimeout(function() {
after && after();
timer = null;
}, wait);
}
}
(二)节流
让函数在一定时间内只触发一次,周期性执行回调,减少一些过快调用
- 适用场景:scroll事件,屏幕resize事件,mousemove事件等需要在用户操作时频繁触发,流畅性要求较高的
function throttle(after, wait) {
/*option说明:after [回调函数];
wait [周期性执行回调间隔时间ms]
*/
var timer;
var isScroll; //是否正在执行回调
return function() {
if (isScroll) return; //在回调函数未执行完以前
isScroll = true;
timer && clearTimeout(timer);
timer = setTimeout(function() {
after && after();
isScroll = false;
timer = null;
}, wait);
}
}
(三)RAF利用浏览器的帧渲染
window.requestAnimationFrame:浏览器的原生功能,告诉浏览器在下一次重绘之前执行回调
- 解决定时器导致掉帧的问题,因为定时器无法准确控制执行回调的时间
#拓展
- 一帧:指浏览器从js执行到绘制画面的过程,动画是由一帧帧静态画面组成
- 目前大多数浏览器渲染更新页面的标准帧率为60次/秒,即FPS为60 frame/s,相当于每帧执行时间为1000/60,约为16.7ms。高于这个数字,页面也只刷新一次,造成性能浪费;低于这个数字,30 FPS以下可能造成卡顿,维持在50~60 FPS的动画比较合适
function rfa(after) {
//option说明:after [回调函数,16.7ms触发一次]
var isScroll;// RAF 触发锁
return function() {
if (isScroll) return;
isScroll = true;
requestAnimationFrame(function() {
after && after();
isScroll = false;
});
}
}
总结
- 定时器由于无法控制回调时间,浏览器可能进行一些复杂计算,导致延迟掉帧;
- requestAnimationFrame方法有效利用了浏览器帧渲染频率,准确控制回调执行时间;
- requestAnimationFrame有一定兼容问题,调用requestAnimationFrame之前需对浏览器进行能力测试,判断是否支持window.requestAnimationFrame;若不支持,用定时器代替;
请戳:demo示例(github) / demo示例(gitee)