实现无缝滚动无滚动条的 Element UI 表格(附完整代码)
在后台管理系统或数据监控场景中,经常需要实现表格无缝滚动展示数据,同时希望隐藏滚动条保持界面整洁。本文将基于 Element UI 实现一个 无滚动条、无缝循环、hover 暂停、状态高亮 的高性能滚动表格,全程流畅无卡顿,适配多浏览器。

最终效果
- 🚀 无缝循环滚动,无停顿、无跳跃
- 🚫 视觉上完全隐藏滚动条,保留滚动功能
- 🛑 鼠标悬浮自动暂停,离开恢复滚动
- 🌈 支持状态字段高亮(如不同状态显示不同颜色)
- 🎨 美观的表格样式,hover 行高亮反馈
- 🛠 高度可配置(行高、滚动速度、表格高度等)
技术栈
- Vue 2 + Element UI(适配 Vue 2 项目,Vue 3 可快速迁移)
- SCSS(样式模块化,便于维护)
实现思路
- 无缝滚动核心:通过「数据拼接」(原数据 + 原数据副本)实现视觉上的无限循环,滚动到原数据末尾时瞬间重置滚动位置,无感知切换
- 隐藏滚动条:多浏览器兼容 CSS 屏蔽滚动条样式,同时预留滚动条宽度避免内容裁剪
- 流畅滚动优化:避免 DOM 频繁重绘,用
scrollTop控制滚动,关闭平滑滚动避免停顿 - 交互增强:hover 暂停滚动、行 hover 高亮、状态字段颜色区分
配置说明
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tableData | Array | [] | 表格数据源(必传) |
columns | Array | [] | 列配置(必传,支持 statusConfig 状态样式) |
rowHeight | Number | 36 | 行高(单位:px) |
scrollSpeed | Number | 20 | 滚动速度(毫秒 / 像素),值越小越快 |
scrollPauseOnHover | Boolean | true | 鼠标悬浮是否暂停滚动 |
tableHeight | Number | 300 | 表格高度(父组件配置) |
完整代码实现
1. 滚动表格组件(SeamlessScrollTable.vue)
<template> <div class="tableView"> <el-table :data="combinedData" ref="scrollTable" style="width: 100%" height="100%" @cell-mouse-enter="handleMouseEnter" @cell-mouse-leave="handleMouseLeave" :cell-style="handleCellStyle" :show-header="true" > <el-table-column v-for="(column, index) in columns" v-bind="column" :key="index + (column.prop || index)" :min-width="column.minWidth || '100px'" > <template slot-scope="scope"> <span v-if="column.statusConfig" :class="getColumnStatusClass(column, scope.row)"> {{ scope.row[column.prop] }} </span> <span v-else> {{ scope.row[column.prop] }} </span> </template> </el-table-column> </el-table> </div> </template> <script> export default { name: 'SeamlessScrollTable', props: { tableData: { type: Array, required: true, default: () => [], }, columns: { type: Array, required: true, default: () => [], }, rowHeight: { type: Number, default: 36, }, scrollSpeed: { type: Number, default: 20, // 滚动速度(毫秒/像素),20-40ms }, scrollPauseOnHover: { type: Boolean, default: true, }, }, data() { return { autoPlay: true, timer: null, offset: 0, combinedData: [], // 拼接后的数据,用于实现无缝滚动 } }, computed: { // 计算表格可滚动的总高度(仅当数据足够多时才滚动) scrollableHeight() { return this.tableData.length * this.rowHeight }, // 表格容器可视高度 viewportHeight() { return this.$refs.scrollTable?.$el.clientHeight || 0 }, }, watch: { tableData: { handler(newVal) { // 数据变化时,重新拼接数据 this.combinedData = [...newVal, ...newVal] this.offset = 0 this.restartScroll() }, immediate: true, deep: true, }, autoPlay(newVal) { newVal ? this.startScroll() : this.pauseScroll() }, }, mounted() { this.$nextTick(() => { // 只有当数据总高度 > 可视高度时,才启动滚动 if (this.scrollableHeight > this.viewportHeight) { this.startScroll() } }) }, beforeDestroy() { this.pauseScroll() }, methods: { handleMouseEnter() { this.scrollPauseOnHover && (this.autoPlay = false) }, handleMouseLeave() { this.scrollPauseOnHover && (this.autoPlay = true) }, startScroll() { this.pauseScroll() const tableBody = this.$refs.scrollTable?.bodyWrapper if (!tableBody || this.tableData.length === 0) return this.timer = setInterval(() => { if (!this.autoPlay) return this.offset += 1 tableBody.scrollTop = this.offset // 关键:当滚动到原数据末尾时,瞬间重置滚动位置到开头 if (this.offset >= this.scrollableHeight) { this.offset = 0 tableBody.scrollTop = 0 } }, this.scrollSpeed) }, pauseScroll() { this.timer && clearInterval(this.timer) this.timer = null }, restartScroll() { this.pauseScroll() if (this.scrollableHeight > this.viewportHeight) { this.startScroll() } }, getColumnStatusClass(column, row) { const statusKey = column.statusField || column.prop const statusValue = row[statusKey] return typeof column.statusConfig === 'function' ? column.statusConfig(statusValue, row) : column.statusConfig[statusValue] || '' }, handleCellStyle() { return { padding: '4px 0', height: `${this.rowHeight}px`, lineHeight: `${this.rowHeight}px`, } }, }, } </script> <style scoped lang="scss"> .tableView { width: 100%; height: 100%; overflow: hidden; ::v-deep .el-table { background-color: transparent; color: #303133; border-collapse: separate; border-spacing: 0; &::before { display: none; } th.el-table__cell.is-leaf { border-bottom: 1px solid rgba(0, 0, 0, 0.1); background: transparent !important; font-weight: 500; color: rgba(0, 0, 0, 0.6); padding: 8px 0; } tr.el-table__row { background-color: transparent; transition: background-color 0.2s ease; &:hover td { background-color: rgba(0, 0, 0, 0.02) !important; } } .el-table__cell { border: none; padding: 4px 0; .cell { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 8px; } } .el-table__body-wrapper { height: 100%; scroll-behavior: auto; &::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; } scrollbar-width: none !important; -ms-overflow-style: none !important; } } ::v-deep .status-warning { color: #e6a23c; font-weight: 500; } ::v-deep .status-danger { color: #f56c6c; font-weight: 500; } ::v-deep .status-success { color: #67c23a; font-weight: 500; } ::v-deep .status-info { color: #409eff; font-weight: 500; } } </style>
2. 父组件使用示例(TableIndex.vue)
<template> <div class="table-container"> <h2 class="table-title">设备状态监控表格</h2> <div class="table-wrapper" :style="{ height: tableHeight + 'px' }"> <!-- 配置滚动参数 --> <seamless-scroll-table :table-data="tableData" :columns="columns" :row-height="36" :scroll-speed="30" /> </div> </div> </template> <script> import SeamlessScrollTable from './SeamlessScrollTable.vue' export default { name: 'DeviceStatusTable', components: { SeamlessScrollTable }, data() { return { tableHeight: 300, // 表格高度可配置 // 表格数据 tableData: [ { id: '1001', name: '设备A', type: '温度', state: '待检查' }, { id: '1002', name: '设备B', type: '压力', state: '已超期' }, { id: '1003', name: '设备C', type: '湿度', state: '已完成' }, { id: '1004', name: '设备D', type: '电压', state: '超期完成' }, { id: '1005', name: '设备E', type: '电流', state: '待检查' }, { id: '1006', name: '设备F', type: '电阻', state: '已超期' }, { id: '1007', name: '设备G', type: '功率', state: '已完成' }, ], // 列配置 columns: [ { prop: 'id', label: '编号', minWidth: '140px' }, { prop: 'name', label: '名称', width: '100px' }, { prop: 'type', label: '设备类型', width: '120px' }, { prop: 'state', label: '状态', width: '100px', statusField: 'state', // 状态样式配置(支持对象/函数) statusConfig: { 待检查: 'status-warning', 已超期: 'status-danger', 已完成: 'status-success', 超期完成: 'status-info', }, }, ], } }, methods: { getStatusClass(state) { const statusMap = { 待检查: 'status-warning', 已超期: 'status-danger', 已完成: 'status-success', 超期完成: 'status-info', } return statusMap[state] || '' }, }, } </script> <style scoped lang="scss"> .table-container { width: 100%; max-width: 500px; margin: 0 auto; padding: 20px; box-sizing: border-box; } .table-title { color: #303133; margin-bottom: 16px; font-size: 18px; font-weight: 500; text-align: center; position: relative; } .table-wrapper { background-color: #ffffff; border-radius: 8px; padding: 16px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); box-sizing: border-box; } </style>
&spm=1001.2101.3001.5002&articleId=154396147&d=1&t=3&u=473b7aa65df64f38853122224a660f50)
464

被折叠的 条评论
为什么被折叠?



