前言
后面有代码以及 demo 地址
在日常开发中, 除了现成插件的使用外, 还有很多问题是只能自己动手的. 先抛出问题, 当一个下拉列表的数据达到几千条甚至上万, 这个时候浏览器已经会出现严重卡顿了. 看看下面的例子
如图所示, 数据量达到 2W 条简单测试数据 (页面没有其他东西), 点击加载下拉列表花了大概 5s 时间. 出现这种情况心里真的是很复杂, 这不是在玩我吗?
解决思路
这个问题其实和表格数据是同一个性能问题, 表格的解决方式是通过分页器来减少页面承载的数据量. 那么下拉列表该如何解决呢? 通常我们都是一次性加载下拉的所有数据的, 针对目前的难题, 思路也是一样, 采用分页来解决页面的性能问题. 问题又来了, 分页器是可以点击的, 那下拉列表又不可以点击, 那就只有在监听滚动事件里实现这件大事了. 先来大纲:
监听滚动
向下滚动时往后加载数据
向上滚动时往前加载数据
数据有进有出
好戏开始
监听滚动
- <el-select class="remoteSelect" v-scroll v-model="value">
- <el-option :value="item.id" v-for="item in list" :key="item.id">{{item.name}}</el-option>
- </el-select>
这里是基于 vue 与 element-ui 中 el-select 实现的监听滚动. 这里是采用自定义指令的方式监听滚动
- // directives 目录下 index.JS 文件
- import Vue from 'vue'
- export default () => {
- Vue.directive('scroll', {
- bind (el, binding) {
- // 获取滚动页面 DOM
- let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
- SCROLL_DOM.addEventListener('scroll', function () {
- console.log('scrll')
- })
- }
- })
- }
在 main.JS 中通过全局方法 Vue.use() 注册使用
- import Directives from './directives'
- Vue.use(Directives)
这时滚动页面就可以看到控制的打印日志, 代表监听已生效, 接下来撸起袖子开干
向下滚动时往后加载数据
首先要先判断出是向上滚动, 还是向下滚动
记录上一次的滚动位置
当前位置与上一次的滚动位置作比较
通过一个公共变量来记录全局位置, 通过 scrollTop 方法获取当前的滚动位置, 并记录在公共变量 scrollPosition 里
- bind (el, binding) {
- // 获取滚动页面 DOM
- let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
- let scrollPosition = 0
- SCROLL_DOM.addEventListener('scroll', function () {
- // 当前的滚动位置 减去 上一次的滚动位置
- // 如果为 true 则代表向上滚动, false 代表向下滚动
- let flagToDirection = this.scrollTop - scrollPosition> 0
- // 记录当前的滚动位置
- scrollPosition = this.scrollTop
- console.log(flagToDirection ? '滚动方向: 下' : '滚动方向: 上')
- })
- }
目前已知晓滚动的方向, 接下来便根据滚动方向做相应的处理. 将滚动行为告诉组件
... 省略
- // 记录当前的滚动位置
- scrollPosition = this.scrollTop
- // 将滚动行为告诉组件
- binding.value(flagToDirection)
事件接受 在 v-scroll 指令中接受事件 v-scroll="handleScroll", 在该方法 handleScroll 处理滚动行为. 接下来只需要在该事件中针对为向下的滚动发起请求数据即可
- /*********************************
- ** Fn: handleScroll
- ** Intro: 处理滚动行为
- ** @params: param 为 true 代表向下滚动
- ** @params: param 为 false 代表向上滚动
- *********************************/
- handleScroll (param) {
- if (param) {
- // 请求下一页的数据
- this.list.push(...this.ajaxData(++this.pageIndex))
- }
- },
到这里滚动加载已经实现. 只是加载太频繁了, 如果快速滚动则会同时发出多个请求后台数据, 在密集一些游览器中 Ajax 就要开发并发排队了, 可见并不理想. 那如何控制呢? 那换种方式触发 handleScroll 事件, 在滚动位置距离滚动页面底部一定高度时在触发, 例如距页面底部只有 100px 时触发 handleScroll 事件
scrollHeight 获取滚动高度
在距底部 100px 时
- // 记录当前的滚动位置
- scrollPosition = this.scrollTop
- const LIMIT_BOTTOM = 100
- // 记录滚动位置距离底部的位置
- let scrollBottom = this.scrollHeight - (this.scrollTop + this.clientHeight) < LIMIT_BOTTOM
- // 如果已达到指定位置则触发
- if (scrollBottom) {
- // 将滚动行为告诉组件
- binding.value(flagToDirection)
- }
通过数据长度的变化可以知道触发事件已经明显和谐了很多, 这种效果很手机懒加载的方式一样, 数据会被不断的叠加.
小提示: 会存在一个 bug, 即 Ajax 是异步的, 如果这个 Ajax 请求花了 1s 才返回数据, 而此时还在继续往下滚, 那就会触发多个请求事件. 如何避免这种情况呢? 答案是增加一个标志位, 在请求前将该标志位设置为 false, 请求结束后设置为 true. 每次请求时先判断该标志位. 如果为 false 则阻止该事件.
中场
再来看看我们的大纲
监听滚动
向下滚动时往后加载数据
向上滚动时往前加载数据
数据有进有出
到这里我们只完成1和2两个步骤. 如果已经满足了你的需求, 那你可以结束阅读了. 如果对你有那么一点点帮助, 先点个赞在离开.
前面说的都还只是基础操作, 还没开始划重点呢. 说好的无性能压力呢?
代码地址: https://github.com/no-simple/selectScroll
当前版本 demo:demo https://no-simple.github.io/selectScroll/
先下班回家吃饭吧. 周末继续写完
来源: https://juejin.im/post/5be534086fb9a049ef2615e2