Element plus 跟随页面的横向滚动条及固定表头

问题

  • 原生的el-table组件的固定表头必须要设置高度,当表格高度需要动态变化的时候该选项无效。
  • 固定列之后出现横向滚动条,但该滚动条也无法跟随页面移动只能固定在表格底部,当数据过多时,无法直接操作。

分析

目前滚动条的解决方案是两种:

  1. 手搓滚动条,过于繁琐了
  2. 第三方组件,目前没找到可以用的

固定表头的方案是动态的计算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'
       }
     }

最终效果

相关推荐
有味道的男人4 小时前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft5 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly5 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝5 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦5 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡6 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI6 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
汉堡大王95276 小时前
单次输出拉到100万Token:拆完 Gemini 4 Argon 的续写协议,才看懂谷歌这7个月在憋什么
前端·人工智能·后端
看浪的路人6 小时前
第9讲:大模型应用监控与可观测性——OpenTelemetry + Prometheus + Grafana 全链路追踪
java·前端·spring
内存过载6 小时前
用表格模拟甘特图:项目排期的土办法
前端·低代码