el-table横向滚动条吸底处理方案思路

# 起因

工作中用到的 el-table显示大批量数据的时候,一页放不下,一般性的处理方案是限定了高,让表格固定表头之后内部可以滚动,不过因为实际操作中样式的问题,需要我把整个表格高度全部显示出来,这样就造成了一个问题,就是横向滚动不方便

不过作为mac用户已开始没啥感觉,但是被用windows的同事吐槽了好久,于是终于决定花半天时间解决

# 仓库

已开源,不想看的,可以直接看代码https://github.com/mizuka-wu/el-table-horizontal-scroll (opens new window) 或者安装npm i el-table-horizontal-scroll用指令的形式使用该项目

# 实现原理

其实是模拟了一个el-scroll进行同步,为了方便,制作成了一个指令

# 核心代码

通过Scroller类,创建一个scrollerdom,并插入到指令提供的el内部

el.appendChild(scroller.dom)

目标对象

const targetTableWrapperEl = el.querySelector('.el-table__body-wrapper')

# Scroller类 具体地址 (opens new window)

主要负责了scroller的创建和管理,具体可以直接看代码

其模拟了一个el-scrolldom结构(为了省的写样式)

大概会出现一个这样的dom结构

<!-- scroll/dom整个dom结构 -->
<div class="el-scrollbar">
	<!-- bar横条容器 -->
	<div class="'el-scrollbar__bar is-horizontal">
		<!-- thumb滑块 -->
		<div class="el-scrollbar__thumb"></div>
	</div>
</div>

然后整体就是对这个结构进行操作

例如,scroll的整体样式需要手动添加,让其可以自由浮动,然后显示在fixed列前,所以需要

scroller.style.height = '12px'
scroller.style.position = 'fixed'
scroller.style.bottom = 0
scroller.style.zIndex = 3

以及一些其他的调整,具体不再阐述

# 自动吸底原理

这个就是目前监听documentscroll事件,判断是否需要显示

核心判断代码就是判断el的底部是否出现在页面内

const viewHeight = window.innerHeight || document.documentElement.clientHeight
const { bottom } = targetTableWrapperEl.getBoundingClientRect() // 当前的el-table的wrapper,可以在el内获取到
if (bottom <= viewHeight) {
   hideScroller()
} else {
  showScroller()
}

平时就position: fixed在页面底部,如果表格底部显示在页面里了,就自动隐藏即可

  • 顺带一提,这个部分因为positonfixed, 所以宽度需要手动设置成eloffsetWidth
  • 如果表格出现宽度变化,也需要重设位置和重新计算滑块宽度

# 滑块相关

滑槽因为默认的样式是opacity: 0所以我顺手做了el根据鼠标事件自动显示和隐藏

滑块主要是需要计算宽度和向左滑动的距离

# 宽度计算

代码如下,我直接放类里面了,具体targetTableWrapperEl是哪个dom可以自己看一下具体的dom结构,表格的容器,因为宽度不变,所以滑块的宽度我们通过可滚动距离和容器宽度换算成一个百分比

因为el-table初始化的问题,初始化计算的时候最好做个延迟,或者监听宽度变化重设宽度,这里我实际代码两个都做了

  /**
   * 自动设置Bar
   */
  resetBar () {
    const { targetTableWrapperEl } = this
    const widthPercentage = (targetTableWrapperEl.clientWidth * 100 / targetTableWrapperEl.scrollWidth)
    const thumbWidth = Math.min(widthPercentage, 100)
    this.thumb.style.width = `${thumbWidth}%`

    if (thumbWidth >= 100) {
      this.fullwidth = true
      this.hideScroller()
    } else {
      this.fullwidth = false
      this.checkIsScrollBottom()
    }
  }
# 滑块距离

具体代码, 原理同样是换算成百分比

resetThumbPosition () {
  this.thumb.style.transform = `translateX(${this.moveX}%)`
}

get moveX () {
  const { targetTableWrapperEl } = this
   eturn ((targetTableWrapperEl.scrollLeft * 100) / targetTableWrapperEl.clientWidth)
}

# 同步滚动距离

# table => scroller

直接在targetTableWrapperElscroll事件里进行设置resetThumbPosition即可

# scroller =》table

分了两个情况,拖滑块 和 点击滑槽

滑槽请自行查看代码

讲一下滑块的原理

记录鼠标滚动距离然后设置给table 然后让table自动更新回scroller

需要三个事件

  • onmousemove
  • onmouseup
  • onmousedown

注意点

  • mousemove建议挂在document
  • 右键 + ctrl这类特殊情况需要处理
# 计算距离

通过e.clientX可以得出每次滚动的offset, 但是这个offset需要换算成具体需要滚动的scrollLeft

targetTableWrapperEl.scrollLeft += offset * rate

然后每个像素的对应的比例,其实可以通过滑块和滑槽的比例直接算出来(两者同样出自容器)

const rate = bar.offsetWidth / thumb.offsetWidth

# 总结

还有一些例如

  • 悬停显示
  • 自动宽度适配

的小功能都在代码中了

欢迎提issue和告诉我那些部分想要自定义的,我可以写在binding