JavaScript触发onScroll事件的函数节流详解


Posted in Javascript onDecember 14, 2016

问题描述

常见的网站布局,顶部一个导航栏,我们假设本页面共有四个栏目:分别为A、B、C、D,我们点击A,锚点跳转至A栏目,同时顶部的A按钮高亮;点击B,锚点跳转至B栏目,同时顶部的B按钮高亮;我们在Main组件里面滚动,滚动到B模块时,B按钮高亮。以上是我们经常会在开发中遇到的一个模型。如果是在以前,用jQuery作前端开发的话,实在是太熟悉不过了。

解决方案

主要想谈谈在React组件化开发中的性能优化方法。

我们的页面结构是这样的

<div
 className={style.main}
 id="main"
 ref={(main) => { this.main = main; }}
 onScroll={
 ((/detail/.test(this.props.location.pathname))) ? (() => this.throttle()()) : null
 }
>
 {this.props.children}
 <Footer />

我们在main组件里设定onScroll事件,在这个事件中,我们触发action,通过redux将状态的变化传递到子组件。

我的scroll事件触发函数是这样的(忽略一长串的if else,这是一个解决了一下午的bug的终极解决方案,此文不做累述)

handleScroll() {
 const { changeScrollFlag } = this.props.actions;
 // 根据滚动距离修改TitleBox的样式
 const { basicinformation, holderinformation, mainpeople, changerecord } = {
 basicinformation: document.getElementById('basicinformation').offsetTop - 121,
 holderinformation: document.getElementById('holderinformation').offsetTop - 121,
 mainpeople: document.getElementById('mainpeople').offsetTop - 121,
 changerecord: document.getElementById('changerecord').offsetTop - 121,
 };
 if (window.screen.availHeight > this.main.scrollTop) {
 document.getElementById('gototop').style.display = 'none';
 } else {
 document.getElementById('gototop').style.display = 'block';
 }
 // 得到基础信息区域、股东信息区域、主要人员区域、变更记录区域的offsetTop,我们把它用来跟main的scrollTop比较
 // 比较的结果触发action,改变TitleBox组件样式
 if (this.main.scrollTop < holderinformation) {
 // 基础信息区域
 if (basicinformation === -121) {
 // 如果基础信息模块不存在,我们什么也不做(当然理论上基础信息模块应该是会有的)
 return;
 }
 changeScrollFlag(1);
 return;
 } else if (this.main.scrollTop < mainpeople) {
 // 股东信息区域
 changeScrollFlag(2);
 if (holderinformation === -121) {
 // 如果股东信息栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为holderinformation
 // 因为holdinformation并不存在,我们跳到前一个按钮,让基础信息按钮高亮
 changeScrollFlag(1);
 return;
 }
 return;
 } else if (this.main.scrollTop < changerecord) {
 // 主要人员区域
 changeScrollFlag(3);
 if (mainpeople === -121) {
 // 如果主要人员栏目不存在,在滚动的时候我们不应该强行把TileBox的高亮按钮设置为mainpeople
 // mainpeople并不存在,我们跳到前一个按钮,让基础信息按钮高亮
 changeScrollFlag(2);
 if (holderinformation === -121) {
 // 如果主要人员栏目不存在,而且连股东信息栏目也没有,我们跳到高亮基础信息栏目
 changeScrollFlag(1);
 return;
 }
 return;
 }
 return;
 } else if (this.main.scrollTop > changerecord) {
 // 与上面同理
 // 变更记录区域
 changeScrollFlag(4);
 if (changerecord === -121) {
 changeScrollFlag(3);
 if (mainpeople === -121) {
 changeScrollFlag(2);
 if (holderinformation === -121) {
  changeScrollFlag(1);
  return;
 }
 return;
 }
 return;
 }
 return;
 }
}

其中,changeScrollFlag()函数是我们的action处理函数。

我们的函数节流

throttle() {
 // onScroll函数节流
 let previous = 0;
 // previous初始设置上一次调用 onScroll 函数时间点为 0。
 let timeout;
 const wait = 250;
 // 250毫秒触发一次
 return () => {
 const now = Date.now();
 const remaining = wait - (now - previous);
 if (remaining <= 0) {
 if (timeout) {
 window.clearTimeout(timeout);
 }
 previous = now;
 timeout = null;
 this.handleScroll();
 } else if (!timeout) {
 timeout = window.setTimeout(this.handleScroll, wait);
 }
 };
}

我们的节流函数返回一个函数,设定一个时间戳,如果我们时间戳的差值较小,我们什么也不做,但我们的时间戳的差值较大,清除定时器,触发scroll函数。这样看起来似乎挺简单,对,确实是挺简单的。

那么在子组件我们还需要怎么做呢?

接收action

二级容器型组件接收action,通过二级容器型组件传递props至三级展示型组件。

我们一定要在componentWillReceiveProps接收到这个props。

记住,在componentWillReceiveProps里使用this.props是并不能够接收到props的变化的!!!组件生命周期函数含有一个自己的参数。

componentWillReceiveProps(nextProps) {
 // 在compoWillReceiveProps里接收到Main组件里所触发onScroll事件的改变activebtn样式的index
 // 并且设置为本组件的state
 this.setState({
 activebtn: nextProps.scrollFlag.scrollIndex,
 });
}

我们的state控制我们高亮的按钮是第几个,它是一个数字。

更改导航条的样式

在这里,我使用了React周边的库:classnames,详情参见其api。

<span
 className={classnames({
 [style.informationactive]: (this.state.activebtn === 1),
 })}
 onClick={() => this.handleClick(1, 'basicinformation')}
>

在此,我们完成了一次从顶层组件触发事件,并做到函数节流,将事件一层层传递至底层展示型组件的一个过程。
最近一些关于前端开发的感慨

  1. 不要在组件中反复调用一个函数,这样会造成巨大的消耗!我们可以通过三元运算符、模板字符串做到的事情,请勿写一个新的函数。
  2. jsx不要太过于冗余。我们尽量写成变量的形式,不然页面结构复杂,不易于我们捕捉bug。
  3. 减少后端请求,能存cookie则存cookie,能存localStorge则存localStorge。
  4. 简单的组件尽量自己写,请勿使用别人的组件,否则在需求更改、样式调整上会出现巨大困难并做一些无意义的事儿。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
js中几种去掉字串左右空格的方法
Dec 25 Javascript
浅谈javascript 面向对象编程
Oct 28 Javascript
JavaScript生成GUID的多种算法小结
Aug 18 Javascript
Jquery zTree 树控件异步加载操作
Feb 25 Javascript
js 获取当前web应用的上下文路径实现方法
Aug 19 Javascript
bootstrap多种样式进度条展示
Dec 20 Javascript
清空元素html(&quot;&quot;) innerHTML=&quot;&quot; 与 empty()的区别和应用(推荐)
Aug 14 Javascript
js+html5 canvas实现ps钢笔抠图
Apr 28 Javascript
Vue CLI项目 axios模块前后端交互的使用(类似ajax提交)
Sep 01 Javascript
JS组件库AlloyTouch实现图片轮播过程解析
May 29 Javascript
openLayer4实现动态改变标注图标
Aug 17 Javascript
JS addEventListener()和attachEvent()方法实现注册事件
Jan 11 Javascript
AngularJS指令与指令之间的交互功能示例
Dec 14 #Javascript
AngularJS指令与控制器之间的交互功能示例
Dec 14 #Javascript
网站申请不到支付宝接口、微信接口,免接口收款实现方式几种解决办法
Dec 14 #Javascript
AngularJS定时器的使用与移除操作方法【interval与timeout】
Dec 14 #Javascript
本地Bootstrap文件字体图标引入却无法显示问题的解决方法
Apr 18 #Javascript
微信小程序中使元素占满整个屏幕高度实现方法
Dec 14 #Javascript
AngularJS基于ngInfiniteScroll实现下拉滚动加载的方法
Dec 14 #Javascript
You might like
PDO防注入原理分析以及使用PDO的注意事项总结
2014/10/23 PHP
php实现遍历多维数组的方法
2015/11/25 PHP
iOS自定义提示弹出框实现类似UIAlertView的效果
2016/11/16 PHP
Referer原理与图片防盗链实现方法详解
2019/07/03 PHP
js 页面执行时间计算代码
2009/03/04 Javascript
老鱼 浅谈javascript面向对象编程
2010/03/04 Javascript
用JS控制回车事件的代码
2011/02/20 Javascript
8个超棒的学习 jQuery 的网站 推荐收藏
2011/04/02 Javascript
javascript 在firebug调试时用console.log的方法
2012/05/10 Javascript
Jquery焦点与失去焦点示例应用
2014/06/10 Javascript
JavaScript实现鼠标滑过处生成气泡的方法
2015/05/16 Javascript
jQuery简单自定义图片轮播插件及用法示例
2016/11/21 Javascript
jQuery手指滑动轮播效果
2016/12/22 Javascript
js前端实现图片懒加载(lazyload)的两种方式
2017/04/24 Javascript
微信小程序 action-sheet 反馈上拉菜单简单实例
2017/05/11 Javascript
仿京东快报向上滚动的实例
2017/12/13 Javascript
vue基于better-scroll实现左右联动滑动页面
2020/06/30 Javascript
uniapp微信小程序:key失效的解决方法
2021/01/20 Javascript
[37:45]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS Orenda
2014/05/22 DOTA
Python时间获取及转换知识汇总
2017/01/11 Python
Django自定义分页效果
2017/06/27 Python
Python利用matplotlib.pyplot绘图时如何设置坐标轴刻度
2018/04/09 Python
在PyCharm中实现关闭一个死循环程序的方法
2018/11/29 Python
Pytorch实现LSTM和GRU示例
2020/01/14 Python
Python打包模块wheel的使用方法与将python包发布到PyPI的方法详解
2020/02/12 Python
基于Python实现体育彩票选号器功能代码实例
2020/09/16 Python
设计师大码女装:11 Honoré
2020/05/03 全球购物
如何查找网页漏洞
2016/06/22 面试题
回门宴新郎答谢词
2014/01/12 职场文书
一月红领巾广播稿
2014/02/11 职场文书
村庄绿化方案
2014/05/07 职场文书
新员工入职欢迎词
2015/01/23 职场文书
2015大学生党员自我评价范文
2015/03/03 职场文书
外出听课学习心得体会
2016/01/15 职场文书
nginx基于域名,端口,不同IP的虚拟主机设置的实现
2021/03/31 Servers
解决pycharm下载库时出现Failed to install package的问题
2021/09/04 Python