主页 > 前端 > javascript >
来源:自学PHP网 时间:2020-12-02 15:57 作者:小飞侠 阅读:次
[导读] js前端对于大量数据的展示方式及处理方法...
今天带来js前端对于大量数据的展示方式及处理方法教程详解
最近暂时脱离了演示项目,开始了公司内比较常见的以表单和列表为主的项目。 现下不得不说是个数据的时代,有数据就必定有前端来展示。 除去花哨的展示方式(图表等),展示普通的大量列表数据有两种常用方式,分页和触底加载(滚动加载)。 分页是一种比较经典的展示方式,碰到的问题比较少,最多是因为一页展示的数据量大些的时候可以用图片懒加载,来加速一些(不过基本一页也不太会超过200个,不然就失去了分页的意义了)。 而最近在实现滚动加载时,出现了卡顿的情况。 问题背景: 数据量:1500左右; 滚动卡顿,往往是动一下滚轮,就要卡个2-3s 分析过程: 卡顿首先想到是渲染帧被延长了,用控制台的Performance查看,可以看出是重排重绘费时间: 如图,Recalculate Style占比远远大于其他,一瞬间要渲染太多的卡片节点,重排重绘的量太大,所以造成了主要的卡顿。 渲染的数据项与图片渲染有关,于是会想到图片资源的加载和渲染,看控制台的Network的Img请求中,有大量的pending项(pending项参考下图所示)。 图片在不停地加载然后渲染,影响了页面的正常运行,因此可以作懒加载优化。 解决过程: 首先针对最主要的减少瞬间渲染量,逐步由简入繁尝试: 1. 自动触发的延时渲染 使用定时器来分页获取数据,然后push进展示的列表数据中: data() { return { count: -1, params: { ... // 请求参数 pageNo: 0, pageSize: 20 }, timer:null, list: [] } }, beforeDestroy() { if (this.timer) { clearTimeout(this.timer) this.timer = null } }, methods: { getListData() { this.count = -1 this.params = { ... // 请求参数 pageNo: 0, pageSize: 20 } this.timer = setTimeout(this.getListDataInterval, 1000) }, getListDataInterval() { params.pageNo++ if (params.pageNo === 1) { this.list.length = 0 } api(params) // 请求接口 .then(res => { if (res.data) { this.count = res.data.count this.list.push(...res.data.list) } }) .finally(() => { if (count >= 0 && this.list.length < count) { this.timer = setTimeout(this.getListDataInterval, 1000) } }) } ... } 结果:首屏渲染速度变快了,不过滚动和事件响应还是略卡顿。 2. 改为滚动触发加载(滚动触发下的“分页”形容的是数据分批次) 滚动触发,好处在于只会在触底的情况下影响用户一段时间,不会在开始时一直影响用户,而且触底也是由用户操作概率发生的,相对比下,体验性增加。 滚动触发“分页”请求数据, 例:结合我本次项目的实际情况,不需要一次性获取所有的数据,可以一次性获取一个时间点的数据,而每个时间点的数据不会超过3600个,这就属于一个比较小的量,尝试下来一次性获取的时间基本不超过500ms,于是我选择第二种 先一次性获取所有数据,由前端控制滚动到距离底部的一定距离,push一定量的数据到展示列表数据中: data() { return { timer: null, list: [], // 存储数据的列表 showList: [], // html中展示的列表 isLoading: false, // 控制滚动加载 currentPage: 1, // 前端分批次摆放数据 currentPageSize: 50, // 前端分批次摆放数据 lastListIndex: 0, // 记录当前获取到的最新数据位置 lastTimeIndex: 0, // 记录当前获取到的最新数据位置 } }, created() { // 优化点:可做可不做,其中的数值都是按照卡片的宽高直接写入的,因为不是通用组件,所以从简。 this.currentPageSize = Math.round( (((window.innerHeight / 190) * (window.innerWidth - 278 - 254)) / 220) * 3 ) // (((window.innerHeight / 卡片高度和竖向间距) * (window.innerWidth - 列表内容距视口左右的总距离 - 卡片宽度和横向间距)) / 卡片宽度) * 3 // *3代表我希望每次加载至少能多出三个视口高度的数据;列表内容距视口左右的总距离:是因为我是两边固定宽度,中间适应展示内容的结构 }, beforeDestroy() { if (this.timer) { clearTimeout(this.timer) this.timer = null } }, methods: { /** * @description: 获取时间点的数据 */ getTimelineData(listIndex, timeIndex) { if ( // this.list的第一、二层是时间轴this.list[listIdex].timeLines[timeIndex],在获取时间点数据之前获取了 this.list && this.list[listIndex] && this.list[listIndex].timeLines && this.list[listIndex].timeLines[timeIndex] && this.showList && this.showList[listIndex] && this.showList[listIndex].timeLines && this.showList[listIndex].timeLines[timeIndex] ) { this.isLoading = true // 把当前时间点变成展示状态 if (!this.showList[listIndex].active) { this.handleTimeClick(listIndex, this.showList[listIndex]) } if (!this.showList[listIndex].timeLines[timeIndex].active) this.handleTimeClick( listIndex, this.showList[listIndex].timeLines[timeIndex] ) if (!this.list[listIndex].timeLines[timeIndex].snapDetailList) { this.currentPage = 1 } if ( !this.list[listIndex].timeLines[timeIndex].snapDetailList // 第一次加载时间点数据,后面的或条件可省略 ) { return suspectSnapRecords({ ... }) .then(res => { if (res.data && res.data.list && res.data.list.length) { let show = [] res.data.list.forEach((item, index) => { show[index] = {} if (index < 50) { show[index].show = true } else { show[index].show = true } }) this.$set( this.list[listIndex].timeLines[timeIndex], 'snapDetailList', res.data.list ) this.$set( this.showList[listIndex].timeLines[timeIndex], 'snapDetailList', res.data.list.slice(0, this.currentPageSize) ) this.$set( this.showList[listIndex].timeLines[timeIndex], 'showList', show ) this.currentPage++ this.lastListIndex = listIndex this.lastTimeIndex = timeIndex } }) .finally(() => { this.$nextTick(() => { this.isLoading = false }) }) } else { // 此处是时间点被手动关闭,手动关闭会把showList中的数据清空,但是已经加载过数据的情况 if ( this.showList[listIndex].timeLines[timeIndex].snapDetailList .length === 0 ) { this.currentPage = 1 this.lastListIndex = listIndex this.lastTimeIndex = timeIndex } this.showList[listIndex].timeLines[timeIndex].snapDetailList.push( ...this.list[listIndex].timeLines[timeIndex].snapDetailList.slice( (this.currentPage - 1) * this.currentPageSize, this.currentPage * this.currentPageSize ) ) this.currentPage++ this.$nextTick(() => { this.isLoading = false }) return } } else { return } }, /** * @description: 页面滚动监听,用的是公司内部的框架,就不展示html了,不同框架原理都是一样的,只是需要写的代码多与少的区别,如ElementUI的InfiniteScroll,可以直接设置触发加载的距离阈值 */ handleScroll({ scrollTop, percentY }) { // 此处的scrollTop是组件返回的纵向滚动的已滚动距离,percentY则是已滚动百分比 this.bus.$emit('scroll') // 触发全局的滚动监听,用于图片的懒加载 this.scrolling = true if (this.timer) { // 防抖机制,直至滚动停止才会运行定时器内部内容 clearTimeout(this.timer) } this.timer = setTimeout(() => { requestAnimationFrame(async () => { // 因为内部有触发重排重绘,所以把代码放在requestAnimationFrame中执行 let height = window.innerHeight if ( percentY > 0.7 && // 保证最开始的时候不要疯狂加载,已滚动70%再加载 Math.round(scrollTop / percentY) - scrollTop < height * 2 && // 保证数据量大后滚动页面长的时候不要疯狂加载,在触底小于两倍视口高度的时候才加载 !this.isLoading // 保险,不同时运行下面代码,以防运行时间大于定时时间 ) { this.isLoading = true let len = this.list[this.lastListIndex].timeLines[ this.lastTimeIndex ].snapDetailList.length // list为一次性获取所有数据存在内存中 if ((this.currentPage - 1) * this.currentPageSize < len) { // 前端分批次展示的情况 this.showList[this.lastListIndex].timeLines[ this.lastTimeIndex ].snapDetailList.push( ...this.list[this.lastListIndex].timeLines[ this.lastTimeIndex ].snapDetailList.slice( (this.currentPage - 1) * this.currentPageSize, this.currentPage * this.currentPageSize ) ) this.currentPage++ } else if ( this.list[this.lastListIndex].timeLines.length > this.lastTimeIndex + 1 ) { // 前端分批次展示完上一波数据,该月份时间轴上下一个时间点存在的情况 await this.getTimelineData( this.lastListIndex, this.lastTimeIndex + 1 ) } else if (this.list.length > this.lastTimeIndex + 1) { // 前端分批次展示完上一波数据,该月份时间轴上下一个时间点不存在,下一个月份存在的情况 await this.getTimelineData(this.lastListIndex + 1, 0) } } this.$nextTick(() => { this.isLoading = false this.scrolling = false }) }) }, 500) }, 结果:首屏渲染和事件响应都变快了,只是滑动到底部的时候有些许卡顿。 3. 滚动触发+图片懒加载 图片懒加载可以解决每次渲染数据的时候因为图片按加载顺序不停渲染产生的卡顿。 // main.js Vue.prototype.bus = new Vue() ... 以下的在template中写js不要学噢 // components/DefaultImage.vue 结果:在点2首屏展示快的基础上,事件交互更快了,触发展示数据也快了。 以上一顿操作之后已经符合本项目的需求了。 |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com