0

【css-tricks译文】动画demo解释 防抖、节流、rAF

 3 years ago
source link: https://www.scarsu.com/debounce_raf/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【css-tricks译文】动画demo解释 防抖、节流、rAF

这三种技术用于优化事件处理函数,都很有用,且各有区别,互相补充。

2020-12-30 | 10技术 | 前端性能 | 2.4k 字 | 8 分钟 | 阅读量 940 条评论

原文链接:防抖与节流的区别

译者注:为了文章更易理解,对原文略有改动


防抖节流是两种相似(但不同)的技术,用于控制一定时间内函数的执行次数。

当我们为DOM事件添加事件处理函数时,防抖或节流函数十分有用。为什么呢?因为我们并不能控制DOM事件被触发的频率,而防抖和节流在事件和事件处理函数之间,为我们添加了一个控制层。

例如scroll事件,看这个demo:

当使用触控板,滚轮,或者滚动条滚动时,每秒钟可以轻易地触发30个事件。但是在我的测试中,在手机中缓慢滑动屏幕,每秒可以触发多达100个事件。你能确保你的事件处理函数在这种执行频率下正常工作吗?

2011年,Twitter网站上出现了一个问题:当你在向下滚动Twitter feed时,网页变得缓慢且无响应。John Resig发表了一篇关于这个问题的博客,文章中解释了将消耗昂贵的函数直接附加到scroll事件上是多么的糟糕。

John建议的解决方案(当时是五年前)是在onScroll事件之外,每隔250ms运行一个循环。这样处理程序就不会与事件耦合。通过这个简单的技术,我们可以避免破坏用户体验。

现如今,有一些更复杂一些的处理事件的方法。让我给大家介绍一下DebounceThrottlerequestAnimationFrame,以及相应的demo。

Debounce 防抖

Debounce 防抖技术允许我们将多次连续的执行”分组”到一次单一的执行中。

https://i0.wp.com/css-tricks.com/wp-content/uploads/2016/04/debounce.png

想象一下这样的场景,你在电梯里,电梯门开始关闭,突然有另一个人想上电梯,电梯则不会运行,门会再次打开。然后又有一个人要上电梯,电梯再次延迟了它的运行(移动楼层),但优化了它的资源。

可以在下面的示例中,尝试在顶部的“Trigger area”中点击或移动:

在上面的示例中可以看到,debounced事件是如何代替一组连续快速触发事件的。但如果事件的触发有很大的时间间隔,则不会发生debouncing。(可以这样理解,如果一直有人要上电梯,电梯就不会运行,直到等待一定时间内无人上电梯,电梯就会开始运行,在上述的示例中,这个等待时间被设定为四个刻度,也就是400ms)

leading / immediate 参数

在上述的示例中,debouncing事件需要等待,直到事件在一定时间内停止触发,才会执行函数。这种场景与等电梯的场景吻合。

如果有另一种场景,需要在事件触发时,就立即执行函数,在快速连续触发的过程中,直到有一个暂停(满足等待时间),才会再次执行函数。

这种需求,可以通过leading参数来实现:(在underscore.js中,这个参数的名称叫 immediate

https://i2.wp.com/css-tricks.com/wp-content/uploads/2016/04/debounce-leading.png

leading”防抖的demo:

Debounce 实现

我第一次看到debounce的Javascript实现是2009年,在John Hann的这篇文章中(他也是这个词的发明者)。

不久之后,Ben Alman创建了一个jQuery插件(不再维护),一年之后,Jeremy Ashkenas将其添加到了underscore.js中。后来,它又被添加到Lodash中,成为undererscore的替代方案。

这3种实现内部有些不同,但它们的接口都差不多。

曾经有一段时间,在我于2013年发现_.debounce函数中的一个bug之后,underscore采用了Lodash的debounce / throttle实现。从那时起,两种实现都有了长足的发展。

Lodash在其_.debounce_.throttle函数中 增加了 更多的功能。原来的immediate 标志被替换为leadingtrailing 选项。你可以选择启用一个,或者两个。默认情况下,只有trailing 被启用。(leading可以理解为,在一组连续触发事件的起始,就调用函数;而trailing,则是在一组连续触发事件的末尾,经过等待时间后,执行函数)

还有一个新的maxWait选项(目前只在Lodash中使用)在本文中没有涉及,但它可能非常有用。

实际上,在Lodash的源码种,throttle节流函数是用通过_.debouncemaxWait选项来定义的。

Debounce 实例

Resize 实例

当调整(桌面端)浏览器窗口的大小时,可能会触发许多的resize事件。

可以在下面的demo中看到:

如你所见,上面的例子中,启用了默认的trailing选项,因为我们只关心用户停止resize后的最终值。

带有AJAX请求的自动填充输入框的输入事件

有一些场景例如等待用户停止输入后再验证其输入,反馈验证信息。这种场景下 _.debounce可以实现:只有当用户停止输入时才发送请求。

此时,leading 标志没有意义,因为我们需要等待至最后的输入。

如何使用debounce 和 throttle 以及 常见陷阱

你可以自己写debounce/throttle函数,或者从一些随机的博客文章中复制它,但我的建议是直接使用 underscoreLodash库。

如果你只需要_.debounce_.throttle函数,你可以使用Lodash自定义构建器来输出一个自定义的2KB minified库。下面时构建命令:

npm i -g lodash-cli
lodash include = debounce, throttle

也就是说,大多数人都是通过webpack/browserify/rollup工具,使用模块化形式的lodash/throttlelodash/debouncelodash.throttlelodash.debounce包。

一个常见的陷阱是,多次调用_.debounce 函数:

// WRONG
$(window).on('scroll', function() {
_.debounce(doSomething, 300);
});

// RIGHT
$(window).on('scroll', _.debounce(doSomething, 200));

在lodash 和 underscore.js中,为debounced饭都处理过的函数创建一个变量,可以调用私有方法 debounced_version.cancel()

var debounced_version = _.debounce(doSomething, 200);
$(window).on('scroll', debounced_version);

// If you need it
debounced_version.cancel();

Throttle 节流

通过使用 _.throttle, 可以限制函数在 X 毫秒内,最多只能执行一次。

debouncing的主要区别在于,节流保证了函数的定期执行,至少每X毫秒一次。

Throttling 实例

无限滚动

举一个很常见的例子,用户正在向下滚动你的无限滚动页面。你需要检查用户离底部有多远。如果用户在底部附近,我们应该通过Ajax请求更多的内容,并将其添加到页面中。

在这种场景下,_.debounce就不适用了,它只有在用户停止滚动时才会触发……而我们需要在用户到达底部之前开始获取内容。而_.throttle可以保证我们不断地检查用户离底部有多远。

requestAnimationFrame (rAF)

requestAnimationFrame 是限制函数执行速度的另一种方式。

它相当于_.throttle(dosomething, 16),但保真度要高很多,因为它是浏览器原生的API,拥有更好的准确性。

可以考虑使用rAF API,作为节流函数的替代品,以下是它的优缺点:

  • 目标为60fps(即每秒60帧),但是由浏览器内部机制决定如何安排渲染的最佳时间。
  • 更简单和更标准的API,未来不会改变,更好维护。
  • rAFs的启用/取消是我们的责任,不像debouncethrottle,是内部管理的。
  • 如果浏览器标签页未激活,它就不会被执行。(对于滚动、鼠标或键盘事件来说,这并不重要)。
  • 虽然所有的现代浏览器都提供了RAF,但在IE9、Opera Mini和旧的Android中仍然不支持。仍然需要polyfill

根据经验来讲,如果我的JavaScript函数是 “绘画 “,或者会直接变更动画相关属性,我会使用requestAnimationFrame,以及在一切涉及重新计算元素位置的地方使用它。

如果要进行Ajax请求,或者决定是否添加/删除一个class(可能会触发CSS动画),我会考虑_.debounce_.throttle,因为可以设置更低的执行速率(例如200ms,而不是16ms)。

rAF 实例

这个demo灵感来自于 Paul Lewis的文章, 文章做他详细解释了demo中的原理和逻辑。

我把rAF16ms 的_.throttle 放在一起进行了比较,结果是它们的性能相似。但是在更复杂的情况下,rAF可能性能会更高。

我在headroom.js库里见到过rAF技术更高级的实例,其中的 逻辑被解耦 并且被包装在了对象中。

debounce防抖, throttle 节流和 requestAnimationFrame 可以用来优化事件处理函数,三种技术都很有用,且各有区别,互相补充:

  • debounce防抖: 将快速连续的多次事件触发分组,归为一次执行。
  • throttle节流: 确保每隔X 毫秒就有一次稳定的执行,例如每200ms检查一次用户滚动位置以触发一个CSS动画。
  • requestAnimationFrame: 节流函数的16ms替代选择。更适用于在页面上重新计算/渲染元素的函数,能得到更平滑的动画。但是注意: IE9 不支持。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK