本文将介绍在使用el-table时,页面滚动时表头的位置自动吸附在顶部
需求
Element Plus的表格组件在使用时,如果表格的内容过多滑动表格,看不到表头的不知道对应的数据是什么,用户体验不太友好,需要将表头固定,但是ELement Plus不支持,因此经过查询觉得可以通过以下几种方式实现该效果
方案一
el-table添加height属性实现表头固定,但是目前该页面的搜索模块已经占了部分面积,如果是小屏幕的电脑表格的视图区会更小,因此考虑一下,滚动到一定位置在给表格设置高度
实现代码如下:
// 滚动的页面
<div ref="orderPageRef" class="page order"></div>
const orderPageRef: any = ref(null)
const scrollHeight = 480
// 注册scroll事件
onMounted(() => {
orderPageRef.value.addEventListener('scroll', handleScroll)
})
// 组件卸载之前移除scroll事件
onBeforeUnmount(() => {
orderPageRef.value.removeEventListener('scroll', handleScroll)
})
const handleScroll = () => {
const top = orderPageRef.value.scrollTop
// 判断滚动的距离是否大于定义的高度,如果大于给表格设置高度,否则移除高度
if (top > scrollHeight) {
tableHeight.value = `calc(100vh - 200px)
tabelRef.value.$el.style = `width:100%;height:calc(100vh - 200px);`
}
else {
tableHeight.value = null
tabelRef.value.$el.style = `width:100%;`
}
}
缺点
- 页面中会存在两个滚动条,页面滚动条和表格滚动条,表格滚动条也可通过操作CSS设置隐藏
- 由于给表格设置了高度,页面滚动条会发生变化,可能会造成页面疯狂抖动
方案二
通过修改
el-table
的CSS样式,使用sticky
粘性定位实现,目前项目中也是通过该方案来实现的 实现代码如下
// 当前页面的高度应该是视图的高度,滚动的页面是order-children,order-children是由内容撑开
<div class="page order">
<div class="order-children">
</div>
</div>
.order {
overflow: auto;
width: 100%;
height: 100%;
:deep(.el-table) {
overflow: visible; // 将el-table的overflow:hidden改为visible
}
:deep(.el-table__header-wrapper) {
position: sticky; // 设置粘性定位
top: 42px; // 距离顶部的位置
z-index: 5;
}
}
方案三
使用自定义指令,通过操作DOM元素,给表头设置
fixed
固定定位
实现代码如下
// main.ts中注册sticky指令
import { createApp } from 'vue'
import App from './App.vue'
import { createSticky } from '~/utils/sticky'
const app = createApp(App)
createSticky(app)
app.mount('#app')
// top: 固定距离顶部的位置 parent: 滚动的元素 zIndex: 设置层级
<el-table
v-sticky="{ top: '64px', parent: '.order', zIndex: 9 }"
:data="tableData"
>
</el-table>
/**
* 思路:通过简单的 el-table的 thead和tbody父级别区域,进行设置对于的fixed
* 1.创建滚动条监听事件,根据滚动条计算表格所在可视窗口位置。设置thead是否固定定位
* 3.监听横向滚动条的属性变化监听。当监听变化时,说明在拖动横向滚动条,需要将设置对应表头位置,防止错位
*/
function getElParentBySelector(el: any, queryClassSelector: string) {
if (!el) {
return el
}
if ([...el.classList].includes(queryClassSelector)) {
return el
}
return getElParentBySelector(el.parentNode, queryClassSelector)
}
// 获取表格的宽度
function getTableShowWidth(thead: string) {
const tableBox = getElParentBySelector(thead, 'el-table')
return tableBox.getBoundingClientRect().width
}
let scrollHandler: any = null
let resizeHandler: any = null
// 当页面滚动时,表头位置也需要滚动
function setScrollWindow(scrollParent?: any, thead?: any) {
requestAnimationFrame(() => {
// 当order滚动了表头位置已经改变了,此时再次触发了window滚动,同步表头的滚动位置,避免错乱
const scrollXPosition = window.scrollX
if (scrollParent.scrollLeft) {
thead.style.left = `${-scrollXPosition - scrollParent.scrollLeft + 72}px`
}
else {
thead.style.left = `${-scrollXPosition + 72}px`
}
})
}
// 页面resize时动态设置表头高度
function setResizeWindow(width?: any, thead?: any, tbody?: any) {
setTimeout(() => {
const theadWrapper: any = document.querySelector('.el-table__header-wrapper')
thead.style.width = `${tbody.offsetWidth < width ? tbody.offsetWidth : width}px`
theadWrapper.style.width = `${tbody.offsetWidth < width ? tbody.offsetWidth : width}px`
})
}
// 设置表头固定位置
function createTableSticky(el: any, binding: any) {
let stickyTop = binding.value.top || 0
const zIndex = binding.value.zIndex || 0
stickyTop = Number.parseFloat(stickyTop)
// 获取表格(element)
let thead = el.querySelector('.el-table__header')
thead = getElParentBySelector(thead, 'el-table__header-wrapper')
const tbody = el.querySelector('.el-scrollbar') || el.querySelector('.el-table__body')
// 获取thead 的显示宽度
let headerShowWidth = getTableShowWidth(thead)
// 获取滚动元素
const scrollParent = document.querySelector(binding.value.parent || 'body')
if (!scrollParent || binding.value.disabled === true) {
return
}
scrollParent.addEventListener('scroll', () => {
requestAnimationFrame(() => {
headerShowWidth = getTableShowWidth(thead)
const theadHeight = thead.clientHeight
// // 获取thead距离顶部的距离
const theadTop = thead.getBoundingClientRect().top
// 判断是否需要回归原来位置
const originally = tbody.getBoundingClientRect().top
// 判断底部距离是否超过表头
const goBeyond = tbody.getBoundingClientRect().bottom
if (theadTop <= stickyTop) {
thead.style.width
= `${tbody.offsetWidth < headerShowWidth ? tbody.offsetWidth : headerShowWidth}px`
thead.style.position = 'fixed'
thead.style.zIndex = zIndex || 1994
thead.style.top = `${stickyTop}px`
thead.style.backgroundColor = '#fff'
}
if (originally - theadHeight > stickyTop || goBeyond - theadHeight / 2 <= stickyTop) {
thead.style.width
= `${tbody.offsetWidth < headerShowWidth ? tbody.offsetWidth : headerShowWidth}px`
thead.style.position = 'static'
}
// 当windows滚动了表头位置已经发生了改变,此时再次滚动order元素,同步表头的滚动位置,避免错乱
const scrollXPosition = scrollParent.scrollLeft
if (window.scrollX) {
thead.style.left = `${-scrollXPosition - window.scrollX + 72}px`
}
else {
thead.style.left = `${-scrollXPosition + 72}px`
}
})
})
// 监听页面window滚动:表头区域动态滚动
scrollHandler = () => setScrollWindow(scrollParent, thead)
window.addEventListener('scroll', scrollHandler, false)
// 监听resize事件:动态设置表头宽度
resizeHandler = () => setResizeWindow(getTableShowWidth(thead), thead, tbody)
window.addEventListener('resize', resizeHandler, false)
}
export function createSticky(vue: any) {
let clearTimeId: any = 0
// el-table表头吸顶效果
vue.directive('sticky', {
// 当被绑定的元素插入到 DOM 中时……
mounted(el: any, binding: any) {
const random = Number.parseInt(`${Math.random() * 10}`)
// TIP 延时设置,确保表格进行渲染成功!
clearTimeId = setTimeout(() => {
createTableSticky(el, binding)
// clearTimeout(clearTimeId)
}, 1000 + random)
},
update(el: any, binding: any) {
const random = Number.parseInt(`${Math.random() * 10}`)
// TIP 延时设置,确保表格进行渲染成功!
clearTimeId = setTimeout(() => {
createTableSticky(el, binding)
// clearTimeout(clearTimeId)
}, 1000 + random)
},
// 指令卸载时,移除scroll和resize事件
unmounted() {
window.removeEventListener('scroll', scrollHandler, false)
window.removeEventListener('resize', resizeHandler, false)
clearTimeId && clearTimeout(clearTimeId)
},
})
}
缺点:
- 需要操作DOM元素注册事件,代码有些许复杂
- 当表头固定在顶部后,切换浏览器控制台,往左侧滚动,右侧有空白区域,目前还没找到原因