移动端返回顶部之分析函数防抖与节流

前言

最近项目手机端的项目需要一个滑屏超出一屏需要出现返回顶部的按钮,并且点击可以回到顶部且消失按钮。
拿到需求之后,很简单嘛,随便写写,可是PC端写完测完之后,搬到手机上就开始KO了。按钮的隐藏和显示执行的很慢,到该到出现或者隐藏的时候会延迟好几秒才有动作。并且这是一个梗,手机在touchmove的时候不会渲染,一定要手指头拿开才行。
然后我就百度百度百度~

防抖(debounce)

定义

防抖技术就是把多个顺序地调用全部合并成一个,也就是说在短时间内,规定这个事件被触发的次数,也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

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
/**
* 500ms内没有连续的触发两次scroll事件,那么会触发我们的真正想执行的事件(显示/隐藏)
* fn 要执行的函数,这里是滚动中的操作动作
* waitTime 延迟的时间
*/
function debounce(fn, waitTime) {
var timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(fn, waitTime);
};
};

/**
* 滚动中的操作
*/
function toggleGoTop() {
if($(window).scrollTop() >= $(window).height) {
$('#goTop').show();
}else {
$('#goTop').hide();
}
}

var scrollHandler = debounce(toggleGoTop, 500);

window.addEventListener('scroll', scrollHandler);

以上的方法可以很好的解决我不想很频繁的去执行toggleGoTop(),但是同时会带来一个问题:我一直在执行scroll事件,我这根本没机会去执行toggleGoTop()呀。
我希望页面在不断的滚动中,scrollHandler可以保证在以一定的频率被触发,这就有了节流的概念。

节流(throttling)

定义

预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。它保证在X秒内至少执行一次scrollHandler事件

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 500ms内一直在触发scrollHandler,那么保证1000ms内能触发一次
* fn 要执行的函数,这里是滚动中的操作动作
* waitTime 延迟的时间
* mustRunTime handler必须执行的时间间隔
*/
function throttle(fn, waitTime, mustRunTime) {
var timer = null,
startTime = new Date()
;
return function() {
var _this = this,
args = [].slice.call(arguments),
currTime = new Date()
;
clearTimeout(timer);

/**
* 如果到了规定的触发时间间隔,就触发toggleGoTop函数
* 否则的话,重新设定定时器(防抖的概念)
*/
if(currTime - startTime >= mustRunTime) {
fn.apply(_this, args);
startTime = currTime;
}else {
timer = setTimeout(fn.bind(null, ...args), waitTime);
}
}
}

/**
* 滚动中的操作
*/
function toggleGoTop() {
// 拿到参数
var args = [].slice.call(arguments);
if($(window).scrollTop() >= $(window).height) {
$('#goTop').show();
}else {
$('#goTop').hide();
}
}

var scrollHandler = throttle(toggleGoTop, 500, 1000).bind(null, 'testArg');

window.addEventListener('scroll', scrollHandler);

触发场景

在我们实际场景中其实很容易就能碰到需要使用这两种技术的场景。
以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

  1. window对象的resize、scroll事件
  2. 拖拽时的mousemove事件
  3. 射击游戏中的mousedown、keydown事件
  4. 文字输入、自动完成的keyup事件(联想搜索)

    实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;
    而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。

扒一扒rAF(requestAnimationFrame)

上面介绍的方法都是借助定时器来实现的,但是如果页面兼容要求不高,只需要兼容高版本或应用在移动端,又或者bad安卓哈哈哈哈,不是我讲坏话,那么可以使用浏览器的原生方法rAF。
关于它的介绍,我之前的文章有提过,这里传送门
这里简单介绍下:

  • 这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数。这个方法接受一个函数为参,该函数会在重绘前调用。
  • rAF 常用于 web 动画的制作,用于准确控制页面的帧刷新渲染,让动画效果更加流畅,当然它的作用不仅仅局限于动画制作,我们可以利用它的特性将它视为一个定时器。(当然它不是定时器)
  • 通常来说,rAF 被调用的频率是每秒 60 次,也就是 1000/60 ,触发频率大概是 16.7ms 。(当执行复杂操作时,当它发现无法维持 60fps 的频率时,它会把频率降低到 30fps 来保持帧数的稳定。)
  • 大致的话,使用requestAnimationFrame相当于throttle(fn, xx, 1000/60)
  • 我感觉假如一直被触发的函数会导致页面重新渲染的DOM的操作的话,还是用requestAnimation吧
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
31
/**
* 保证scrollHandler以16.7ms的频率至少触发一次
*/
var requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
},
ticking = false//raF触发锁
;
function scrollHandler() {
if(!ticking) {
requestAnimationFrame(toggleGoTop);
ticking = true;
}
}

/**
* 滚动中的操作
*/
function toggleGoTop() {
if($(window).scrollTop() >= $(window).height) {
$('#goTop').show();
}else {
$('#goTop').hide();
}
ticking = false;
}

window.addEventListener('scroll', scrollHandler, false);

总结

没啥总结的,感觉都讲完了,上面也挺细致。那就祝自己能更上一层楼吧!博客产量能多点。