问题
- 原生的el-table组件的固定表头必须要设置高度,当表格高度需要动态变化的时候该选项无效。
- 固定列之后出现横向滚动条,但该滚动条也无法跟随页面移动只能固定在表格底部,当数据过多时,无法直接操作。
分析
目前滚动条的解决方案是两种:
- 手搓滚动条,过于繁琐了
- 第三方组件,目前没找到可以用的
固定表头的方案是动态的计算max-height,理论是可以的,但在我的项目无效
解决方案
滚动条
我的整体思想是在不破坏原有滚动条的情况下,更改它的定位来实现随页面滚动
它的滚动条的定位是当通过position:absolute 定位到表格底部,以及display来控制显示与否。
属性scrollbar-always-on可以来解决不显示的问题。
定位的解决方案是:切换absolute和fixed定位来控制滚动条的位置。当超过表格的高度或者表格不可见时,使其保持他原生的定位,反之则固定在页面底部。
由于滚动条并不是出现在表格内部,所以是给它的外部容器添加滚动事件。
其中elScrollBarH
是滚动条的外部容器,并不是滚动条本身 ,tableContent
则是表格的内容区域,用于给滚动条定位
ts
/**
* @description: 设置滚动条的位置
* 这里调整的是滚动条的包裹容器,他相对于表格容器来定位,
* 而内部的实际的滚动条相对于这个容器使用tranlate来调整位置
* 所以在调回的时候,直接把left设置为0,仍然能保持滚动条的相对位置
* @param {*} state true:固定在可视区域,false:固定在表格容器内
* @return {*}
*/
const setScrollPos = (state: boolean) => {
if (state) {
if (!elScrollBarH.value || !tableContent.value) return
elScrollBarH.value.style.position = 'fixed'
// 这里去找表格的content区域,把他相对于视口的left值设置给滚动条的容器
elScrollBarH.value.style.left =
tableContent.value?.getBoundingClientRect().left + 'px'
} else {
elScrollBarH.value!.style.left = 0 + 'px'
elScrollBarH.value!.style.position = 'absolute'
}
}
重点在于state条件的判断,为true的情况有只有一种,表格出现在视口内且滚动高度没有超过表格所在高度 。为此,需要两个部分的判定,首先是滚动的时候需要对高度进行判断 ,第二需要一个可见性判断。
ts
/**
* @description: 判定当前横向滑动条是否在表格内
* 滑动距离加可见区域高度不大于表格总高度,则说明在表格内
* @return {*}
*/
const isIntable = () => {
if (!tableContent.value || !tableContainer.value) return false
const { scrollTop, offsetHeight, scrollHeight } =
tableContainer.value as HTMLElement
let result = scrollTop + offsetHeight < scrollHeight
isFixed.value = result // 这个一会儿解释
return result
}
<!---->
/**
* @description: 观察表格是否在可视区域,设置滚动条的对应位置
* @param {*} entries 观察实例,包含当前观察的元素的状态
* @return {*}
*/
const obScroll = (entries: IntersectionObserverEntry[]) => {
setScrollPos(entries[0].intersectionRatio > 0) // 这里他是一个数组,但我只有一个滚动条所以就直接访问0了
}
// 表格可见性的观察者
const tableVisOb = new IntersectionObserver(obScroll)
到这里已经可以正常使用滚动条了,但是当表格宽度发生变化的时候,滚动条并不会跟随表格调整位置,所以还需要对表格尺寸变化进行监视
ts
// 当前滚动条是否固定状态,这个主要用于在给sizeOb使用
// 如果是固定状态,那么在表格大小改变的时候,left需要重置到表格的最左侧的left位置
// 如果不是,那么设为0即可
// 这也是上方出现这个变量的原因
const isFixed = ref<boolean>(true)
/**
* @description: 生成一个表格大小的观察者,当大小变化去动态的调整滚动条的位置
* @param {ResizeObserverEntry[]} entries 观察的实例
* @return {*}
*/
const tableSizeOb = new ResizeObserver((entries: ResizeObserverEntry[]) => {
if (!elScrollBarH.value || !tableContainer.value) return
// 找到表格容器
let container = entries.find(item => item.target === tableContainer.value)
if (!container) return
// 看当前的固定状态,分别去调整滚动条的位置
if (isFixed.value) {
let left = container.target.getBoundingClientRect().left
elScrollBarH.value.style.left = left + 'px'
} else {
elScrollBarH.value.style.left = 0 + 'px'
}
})
最后记得挂载
onMounted(() => {
tableVisOb.observe(tableContent.value as HTMLElement)
tableSizeOb.observe(tableContainer.value as HTMLElement)
})
表头
表头的解决方案就很简单粗暴了,直接计算滑动高度来固定表头及还原表头位置,这个方案会让表头样式有一点点问题,可以调整(但我没调)
ts
if (tableHeaderRef.value) {
const { scrollTop } = tableContainer.value as HTMLElement
let contentOffsetTop = tableContent.value!.offsetTop // 父容器距离顶部的高度
let contentScollHeight = tableContent.value!.scrollHeight // 整个父容器的高度
// 超出表头原本位置时且小于整个表的高度,则固定表头
if (
scrollTop >= contentOffsetTop &&
scrollTop <= contentOffsetTop + contentScollHeight
) {
tableHeaderRef.value.style.position = 'fixed'
tableHeaderRef.value.style.top = '125px' // 这里位置是随便给的,可以通过计算顶部导航高度来确定准确的值
tableHeaderRef.value.style.zIndex = '666'
} else {
// 还原
// 不设置top是因为在static下,top无效
tableHeaderRef.value.style.position = 'static'
tableHeaderRef.value.style.zIndex = '1'
}
}