最近一段时间,在项目中实现过瀑布流、懒加载、侧边栏导航等功能。总觉得在处理浏览器scroll滑动的时候有点问题,通过计算各个模块的高度和某些指定模块的出现时机时,在PC端还好,在移动端容易出现卡顿和抖动的情况。特此整理解决方案。
如果我们需要在滑动的时候进行某些操作,可以在停止滑动后再延迟进行,这样就不会一边滑动一边执行了。
//滑动停止后延迟wait毫秒后才执行func
function debounce(func, wait) {
// 定时器变量
var timeout;
return function() {
// 每次触发 scroll handler 时先清除定时器
clearTimeout(timeout);
// 指定 xx ms 后触发真正想进行的操作 handler
timeout = setTimeout(func, wait);
};
};
// 实际想绑定在 scroll 事件上的处理函数
function realFunc(){
console.log("Success");
}
// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
//window.addEventListener('scroll',realFunc);
还可以更好的封装一番:
// 防抖动函数
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate & !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// 滚动中的真正的操作
}, 250);
// 绑定监听
window.addEventListener('scroll', myEfficientFn);
如果我们不是在滑动停止后执行,而是在滑动中需要实时计算一些东西,就可以采用 节流 的方式。
节流函数,只允许一个函数在 X 毫秒内执行一次。
// 简单的节流函数
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if(curTime - startTime >= mustRun){
func.apply(context,args);
startTime = curTime;
// 没达到触发间隔,重新设定定时器
}else{
timeout = setTimeout(func, wait);
}
};
};
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
console.log("Success");
}
// 采用了节流函数
window.addEventListener('scroll',throttle(realFunc,500,1000));
如果在一段时间内 scroll 触发的间隔一直短于 500ms ,那么能保证事件我们希望调用的 handler 至少在 1000ms 内会触发一次。
上面的两种方式都是通过setTimeout来实现的,精度不够高,如果对浏览器兼容性要求不高,或者是移动端web,可以使用原生的 requestAnimationFrame
来实现。
该方法的原理: 在浏览器的页面重绘之前,通知浏览器调用一个指定的函数。该方法被调用的频率为每秒60次 。
所以说用该方法来触发滚动事件,相当于上面的:
throttle(func, xx, 1000/60) //xx 代表 xx ms内不会重复触发事件 handler
var ticking = false; // rAF 触发锁
function onScroll(){
if(!ticking) {
requestAnimationFrame(realFunc);
ticking = true;
}
}
function realFunc(){
// do something...
console.log("Success");
ticking = false;
}
// 滚动事件监听
window.addEventListener('scroll', onScroll, false);
这样浏览器就会以16.7ms的频率触发事件。
至于移动端,最好还是使用iscroll这样的模拟事件滑动的库来解决其延迟问题吧。