xiaomibaobao

onscroll事件稀释所带来的思考

为什么onscroll事件、要做这个稀释呢?浏览器中的onscoll事件执行的太频繁了,当一个页面上有很多个onscroll事件的时候,就会造成页面性能的大幅度下降。

  • 这个稀释可以有三个语义
  1. “稀释”——稀释的是onscroll相对于浏览器本身的触发。
  2. “稀释”——稀释的是onscroll回调的执行次数。
  3. “稀释”——稀释的是绑定给onscroll中具体想要执行的业务逻辑的执行次数。

方案

  • 第一种解决方案:需求是一个logo,不在第一时间显示,当滑动高度超过50的时候,让这个logo出现.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let i =0 ;
window.onscroll = function () {
i++;
if (i == 50) {//dilute the onscroll;
console.log('scroll is on ');
i = 0;
let quxueche = document.getElementsByClassName("log")[0];
let top = document.documentElement.scrollTop || document.body.scrollTop;
if (top > 50)
logo.style.display = "block";
else
logo.style.display = "none";
}
};
但是这种方案有缺点,不能保证这个方法一定会被执行,就是当你滚动过少的时候,会造成i加不到设定值,无法启动绑定的方法。
  • 第二种解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let timer = null;
window.addEventListener('scroll', function () {
if (typeof timer === 'number') {
clearTimeout(timer);
}
timer = setTimeout(function () {
console.log('scroll is on ');
let quxueche = document.getElementsByClassName("quxueche")[0];
let top = document.documentElement.scrollTop || document.body.scrollTop;
if (top > 50)
quxueche.style.display = "block";
else
quxueche.style.display = "none";
}, 300);
}, false);
这种解决方案相对合理,可以保证函数确定被运行,并且有一定的间隔。
  • 第三种解决方案
1
2
3
4
5
6
7
8
9
10
11
var cb = {
onscroll:function() {
console.log("scrolling");
window.removeEventListener("scroll", cb.onscroll, false);
setTimeout(function() {
console.log("DONE");
window.addEventListener("scroll", cb.onscroll, false);
}, 200);
}
};
window.addEventListener("scroll", cb.onscroll, false);
这个解决方案是网上抄的,貌似是最佳的,可以本质上减少对onscroll的绑定,而并非只是减少回调。

总结

  1. 浏览器事件的callback会block浏览器自身的行为, 也就是比如一个scroll触发的callback要执行一秒,那么浏览器会等callback执行的一秒结束后,才会开始执行自己的scroll行为。这样的话如果每滑动一次就执行一次, 这个页面就废了。 所以要尽量保证callback能立即执行完(及时有更大的运算也要扔到另一个function作为callback)
  2. 浏览器的渲染是有一个频率的, 比如你一秒一个动画执行了80帧, 而浏览器只支持60帧, 那么中间会有剩下的那20帧浪费了, 也会中间失去的那20帧也会造成很突兀的视觉效果。所以最好的策略是浏览器每渲染一次之前就执行一次动画。
  3. scroll事件是每次滚动的时候都会触发, 很多时候我们只需要一个结果很一个很平滑的视觉过度,而中间有很多不必要的执行过程, 这些就是需要“稀释”的地方。

库的实现

Underscore.js里有2个比较相似的方法,一个是函数节流,.debounce(function, wait, [immediate]) ,即若第一个函数参数下一次执行与上次执行在固定时间wait间隔内的话就不会执行,这种状态一直持续下去直到事件不再触发则执行最后一次;还有一种是固定时间间隔的执行函数,.throttle(function, wait, [options]) ,类似于setInterval()。

我自己的模仿实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
*
* @param func
* @param delay
* @returns {Function}函数稀释 - 只在最后执行一次
*/
debounce: function (func, delay) {
return function () {
clearTimeout(func.timer);
func.timer = setTimeout(function () {
func.call();
}, delay)
}
},
/**
*
* @param func
* @param delay
* @returns {Function}函数稀释,固定缓冲时间稀释。
*/
throttle: function (func, delay) {
let before = new Date();
return function () {
let now = new Date();
if (now - before >= delay) {
func.call();
before = now;
}
}
},
如果我实实在在的帮到您,可以点击打赏,请我喝一杯。