大数据量表格渲染初探

高性能表格

前言

前端对于大数据量表格高效渲染需求

前端实现表格主要有以下两种方式:
  • Canvas实现 - Canvas 渲染效率比 DOM 高
  • DOM实现 - DOM 可拓展性比 Canvas 好,渲染自定义内容首选 DOM 而非 Canvas。

最终我们尝试用DOM来探究大数据量表格的渲染

效果如下(表格数据来自本地70w条数据,网络请求的话只要后台能及时返回数据渲染压力应该不会太大,没有时间做很多,只是实现了原理级别的内容):

20220612_224420

实现原理:

1.div作为单元格,采用绝对布局

2.requestAnimationFrame

3.translateY

4.虚拟滚动

5.编辑点击时候替换div

6.需要固定的行列单独渲染

代码

<template><div v-for="row,index in headerData" :key="item"><div class="headerLine" style=""  v-for="column,key,i in row"  ><div class="header" :style="'position: fixed; top: 60px; left:' + i * 300 +'px;'">{{ column }}</div></div></div><div class="wrapper" ref="wrapper" @scroll="onScroll"><div class="background" :style="{height:`${total_height}px`}"></div><div  class="table" ref="container"><div v-for="row,index in runList" :key="item"><div  class="line" v-for="column,key,i in row.data"  ><div @dblclick="itemClick" :style="'top:' + index * (row.rospan * 50) +'px; z-index:' + (runList.length - index) +';left:' + i * 300 +'px;'" :class="[getClass(index), 'item']">{{ column === '' ? 'NULL':column }}</div></div></div></div></div>
</template><script>
import axios from 'axios'
import areaCodeData from './area_code.json'export default {props: {cache_screens: { // 缓冲的屏幕数量type: Number,default: 1}},data () {return {city_data: null,headerData: [{id: '表id', code: '区划代码',name: '名称',level: '级别1-5,省市县镇村',pcode: '父级区划代码',create_time: '创建时间',created_by: '创建人',update_time: '更新时间',updated_time: '更新人'}],list: [], // 源数据runList: [], // 运行时的列表total_height: 0, // 列表总高度maxNum: 0,// 一屏幕容纳的最大数量distance: 0 // 存储滚动的距离}}, created() {this.getApiData()},mounted () {this.genData()this.init();this.getRunData();},methods: {getApiData() {//   axios.get('http://www.licona.club:8000/gperp/basic-info/area-code/list/?page=1&pageSize=10000')//   .then(resp=>{//     console.log(1111)//     this.city_data = resp.data.result.results//     console.log(this.city_data.length)//     this.genData()//         this.init();// this.getRunData();//   })//   .catch(error=>{//       console.log(error);//       alert('网络错误,不能访问');//   })console.log(areaCodeData)this.city_data = areaCodeDataconsole.log(this.city_data)},getClass (column) {switch (column) {case 0: return "top_line";default:return "";}},getPosition(row) {return 'top:' + row.top  + 'px;bottom:100px; left: 200px;'},init () {console.log(this.$refs)const containerHeight = parseInt(getComputedStyle(this.$refs.wrapper).height);//一屏的最大数量this.maxNum = Math.ceil(containerHeight / this.min_height);console.log(`maxNum:${this.maxNum}`);},onScroll (e) {if (this.ticking) {return;}this.ticking = true;requestAnimationFrame(() => {this.ticking = false;})const distance = e.target.scrollTop;this.distance = distance;this.getRunData(distance);},//二分法计算起始索引getStartIndex (scrollTop) {let start = 0, end = this.list.length - 1;while (start < end) {const mid = Math.floor((start + end) / 2);console.log(this.list[mid])const { top, height } = this.list[mid];console.log(top)console.log(height)if (scrollTop >= top && scrollTop < top + height) {start = mid;break;} else if (scrollTop >= top + height) {start = mid + 1;} else if (scrollTop < top) {end = mid - 1;}}return start;},getRunData (distance = null) {console.log(this.$refs.container)//滚动的总距离const scrollTop = distance ? distance : this.$refs.container.scrollTop;//在哪个范围内不执行滚动if (this.scroll_scale) {if (scrollTop > this.scroll_scale[0] && scrollTop < this.scroll_scale[1]) {return;}}//起始索引console.log(scrollTop)let start_index = this.getStartIndex(scrollTop);console.log(start_index)start_index = start_index < 0 ? 0 : start_index;//上屏索引let upper_start_index = start_index - this.maxNum * this.cache_screens;upper_start_index = upper_start_index < 0 ? 0 : upper_start_index;// 调整offsetthis.$refs.container.style.transform = `translate3d(0,${this.list[upper_start_index].top}px,0)`;//中间屏幕的元素const mid_list = this.list.slice(start_index, start_index + this.maxNum);// 上屏const upper_list = this.list.slice(upper_start_index, start_index);// 下屏元素let down_start_index = start_index + this.maxNum;down_start_index = down_start_index > this.list.length - 1 ? this.list.length : down_start_index;this.scroll_scale = [this.list[Math.floor(upper_start_index + this.maxNum / 2)].top, this.list[Math.ceil(start_index + this.maxNum / 2)].top];const down_list = this.list.slice(down_start_index, down_start_index + this.maxNum * this.cache_screens);this.runList = [...upper_list, ...mid_list, ...down_list];},//生成数据genData () {function getHeight () {return 50;}let total_height = 0;console.log(this.city_data)const list = this.city_data.map((data, index) => {const height = getHeight(data.type);const ob = {index,height,top: total_height,data,rospan: 1}// if(index === 0) {//   console.log(data)//   ob["rospan"] = 2// }total_height += height;return ob;})this.total_height = total_height; //  列表总高度this.list = list;this.min_height = 50; // 最小高度是50},itemClick(event) {this.replace(event.target)},replace(divElement) {var input = document.createElement("input");input.className = "edit-input"; input.id = "edit-input"; input.value = divElement.innerHTMLinput.style = "height: 49px; width: 100%"divElement.innerHTML = ''divElement.appendChild(input);input.focus()input.onblur = function() {divElement.innerHTML = input.value;}} }
}
</script>
<style lang="scss" scoped>
.wrapper {position: absolute;left: 0;right: 0;bottom: 0;top: 110px;overflow-y: scroll;.background {position: absolute;top: 0px;left: 0;right: 0;z-index: -1;}.list {position: absolute;top: 0px;left: 0;right: 0;}
}
.headerLine {border-top: 1px solid gray;border-right:1px solid gray;position: fixed;text-align: center;box-sizing: border-box;font-size: 12;background-color: gray;right: 50px;.header {position: fixed;// border-bottom:1px solid gray;border-top:1px solid gray;border-right:1px solid gray;width: 300px;line-height: 49px;height: 49px;background-color: gray;color: white;z-index: 99999 !important;}
}.top_line {border-top: 1px solid gray;
}
.bottom_line {border-top: 1px solid gray;
}
.bottom_right {border-top: 1px solid gray;
}
.line {border-top: 1px solid gray;border-right:1px solid gray;position: absolute;text-align: center;box-sizing: border-box;font-size: 12;.item {position: absolute;// border-bottom:1px solid gray;border-bottom:1px solid gray;border-right:1px solid gray;width: 300px;line-height: 49px;&.lt {margin-left: 10px;}&.gt {margin-right: 10px;}}.item:hover {background-color: aqua;}
}.edit-input {height: 49px;width: 100%;
}
</style>

结论

降低不可见区域DOM树渲染可以成倍提高前端体验。

git地址

参考地址1
参考地址2


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部