ElementUI中的el-table实现虚拟滚动 | 一篇讲清什么是虚拟滚动,该如何实现 | Vue2

为什么要用虚拟滚动

当表格不以分页形式展示,如果数据过多,那么dom树会将数据节点全部加载出来,如下图所示: 假如从后端获取的数据有成千上万条导致了页面卡顿,又不想改成分页展示,那么你需要用"虚拟滚动 "来处理。 虚拟滚动 能够实现展示所有数据,但是不在DOM树中渲染全部节点,进而优化了性能!如下图所示

什么是虚拟滚动

先知道什么是真实滚动,发生滚动前tbody顶部正好紧贴于表格内部。 当你滚动后,效果如下: 第一个节点跑到了表格的上面,也就是说tbody的位置发生了位移,这就是真实的滚动。
所谓虚拟滚动,顾名思义不是tbody在表格中位移,而是用另一个元素在tbody代替tbody滚动。

虚拟滚动实现原理

如下图所示,限制表格只渲染表格可视区域个数的数据节点,然后在el-tableel-table__body-wrapper 的盒子内塞一个div,你可以理解为是伪造的tbodydiv的高度 = 一行单元格的高度 * 表格数组长度 发生滚动时,伪造的tbody进行滚动,而真正的tbody纹丝不动,只改变tbody中已选染的数据节点(个数保持不变)。 发生滚动时,通过计算得出展示数据的索引值,然后在存储所有展示数据的数组里,截取部分数据出来展示,如此虚拟滚动基本功能已实现。

Demo

kotlin 复制代码
<template>
    <div>
        <div class="gl-cell-card-box">
            <el-table
                ref="tableRef"
                style="width:418px"
                border
                max-height="480"
                :data="sliceTable"
                :row-key="row => row.id"
                @select="handleSelect"
                @select-all="handleSelectAll"
            >
                <el-table-column type="selection" width="40"> </el-table-column>
                <el-table-column prop="name" label="姓名" width="120"></el-table-column>
                <el-table-column prop="age" label="年龄" width="120"></el-table-column>
                <el-table-column prop="address" label="住址"></el-table-column>
            </el-table>
        </div>
    </div>
</template>

<script>

export default {
    data() {
        return {
            // 表格所有数据
            tableData:[],
            // 开始索引
            startIndex: 0,
            // 选中的数据
            selectedRows: [],
            // 空元素,用于撑开table的高度
            vEle: undefined,
            // 是否全选
            isSelectedAll: false,
        };
    },

    // 计算属性
    computed: {
        // 这个是截取表格中的部分数据,放到了 table 组件中来显示
        sliceTable() {
            return this.tableData.slice(
                this.startIndex, ((this.tableData.length - this.startIndex) > 9) ? (this.startIndex + 9) : this.tableData.length);
        },
    },
    created() {
        // 创建一个空元素,这个空元素用来撑开 table 的高度,模拟所有数据的高度
        this.vEle = document.createElement("div");
    },
    mounted() {
        this.getData();
        this.loadData();
        // 绑定滚动事件
        this.$refs.tableRef.$el
            .querySelector(".el-table__body-wrapper")
            .addEventListener("scroll", this.tableScroll, {
            passive: true
        });
    },
    methods: {
        getData(){
            let start_i = this.tableData.length;
            console.log(start_i);
            for (let i = start_i; i < start_i + 200; i++) {
                this.tableData.push({
                    id: i,
                    name: "zhangsan" + i,
                    age: 12,
                    address: "china"
                });
            }
            console.log(this.tableData);
        },

        // 加载数据
        loadData() {
            this.$nextTick(() => {
                // 设置成绝对定位,这个元素需要我们去控制滚动
                this.$refs.tableRef.$el.querySelector(".el-table__body").style.position = "absolute";
                // 计算表格所有数据所占内容的高度
                this.vEle.style.height = this.tableData.length * 48 + "px";
                // 把这个节点加到表格中去,用它来撑开表格的高度
                this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper").appendChild(this.vEle);
                // 重新设置曾经被选中的数据
                this.selectedRows.forEach(row => {
                    this.$refs.tableRef.toggleRowSelection(row, true);
                });
            });
        },

        /**
         * @description: 手动勾选时的事件
         * @param {*} selection - 选中的所有数据
         * @param {*} row - 当前选中的数据
         * @return {*}
         */
        handleSelect(selection, row) {
            this.selectedRows = selection;
        },

        /**
         * @description: 全选事件
         * @param {*} selection
         * @return {*}
         */
        handleSelectAll(selection) {
            tis.isSelectedAll = !this.isSelectedAll;
            if (this.isSelectedAll) {
                this.selectedRows = this.tableData;
            } else {
                this.selectedRows = [];
                this.$refs.tableRef.clearSelection();
            }
        },

        /**
         * @description: table 滚动事件
         * @param {*}
         * @return {*}
         */
        tableScroll() {
            let bodyWrapperEle = this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper");
            // 滚动的高度
            let scrollTop = bodyWrapperEle.scrollTop;
            // 下一次开始的索引
            this.startIndex = Math.floor(scrollTop / 48);
            console.log(scrollTop, this.startIndex,'scrollTop, startIndex');
            // 滚动操作
            bodyWrapperEle.querySelector(".el-table__body").style.transform = `translateY(${this.startIndex * 48}px)`;
            // 滚动操作后,上面的一些 tr 没有了,所以需要重新设置曾经被选中的数据
            this.$nextTick(() => {
                this.selectedRows.forEach(row => {
                    this.$refs.tableRef.toggleRowSelection(row, true);
                });
            })
            // 滚动到底,加载新数据
            if (bodyWrapperEle.scrollHeight <= scrollTop + bodyWrapperEle.clientHeight) {
                this.$message.warning("没有更多了");
                return;
            }
        }
    },

}

</script>

参考文献

blog.csdn.net/Hyanl/artic...

相关推荐
从零开始学习人工智能1 小时前
FastMCP:构建 MCP 服务器和客户端的高效 Python 框架
服务器·前端·网络
烛阴1 小时前
自动化测试、前后端mock数据量产利器:Chance.js深度教程
前端·javascript·后端
好好学习O(∩_∩)O1 小时前
QT6引入QMediaPlaylist类
前端·c++·ffmpeg·前端框架
敲代码的小吉米1 小时前
前端HTML contenteditable 属性使用指南
前端·html
testleaf1 小时前
React知识点梳理
前端·react.js·typescript
站在风口的猪11081 小时前
《前端面试题:HTML5、CSS3、ES6新特性》
前端·css3·html5
Xiao_die8881 小时前
前端八股之CSS
前端·css
每天都有好果汁吃2 小时前
基于 react-use 的 useIdle:业务场景下的用户空闲检测解决方案
前端·javascript·react.js
穗余2 小时前
NodeJS全栈开发面试题讲解——P10微服务架构(Node.js + 多服务协作)
前端·面试·node.js
横冲直撞de2 小时前
前端下载文件,文件打不开的问题记录
前端