基于ElementUI的el-table二次开发,使表头自动吸顶(支持左右fixed)

2,656 阅读3分钟

效果图

在线预览地址:lsz579.github.io/element-tab…

在这里插入图片描述

具体需求

在使用el-table时,表格高度自适应(不出现双滚动条,仅body有滚动条),滚动到表头的位置时自动吸附在顶部。

实现原理

在尝试了几种方法之后,其他方法都不支持左右固定栏(fixed),所以最终采用了监听滚动事件的方案,采用了mixins混入方案,只测试vue2.0,不建议使用全局混入,vue3估计需要调整** 1、通过监听滚动事件、获取表头的高度,位置,在特定的情况下修改el-table-header的style属性 2、由于考虑到使用keep-alive,所以每个表格组件都需要加一个唯一的class值,用于获取当前唯一的表格的dom

使用方法

1.设置滚动的节点 sticky-table.js文件

scrollClass:'.main-container',//设置滚动的body

注意把滚动的body设置成可滚动

.main-container {
  height: 100vh !important;
  overflow: auto;
}

2.引入并注入sticky-table mixins,设置当前表格组件的父元素class,即parent headerWidth是用于生成自定义表格滚动条的(固定在底部,详情看git上的例子)

import stacky from '@/utils/table-sticky'
export default{
 	mixins: [stacky],
 	data() {
	   return {
	     tableColumns: [],
	     tableData: [],
	     headerWidth: 0,
	     parent: 'self-table',
             tableWidth:0
	   }
  },
}
  1. 暴露出来的方法,可直接this.xx调用,大多数用于动态修改表头或者拖动的时候,需要更新表格样式
方法名说明
clearListener清空sticky监听事件,页面卸载等情况使用
updateFixedRight更新右侧固定栏样式
headerDragend拖拽改变表头宽度,更新表格宽度,或有表格改动时导致的样式错乱使用
setScrollXWidth用于更新tableWidth,headerWidth,用户制作自定义滚动条
clearFixedStyle清除固定顶部的样式
initFixedHeader初始化mixins
  1. 加入模拟表格滚动条固定到底部 实例上有提供该组件(virtualScroll), 方法setScrollXWidth是计算滚动条的宽度的,headerWidth是表头实际长度,tableWidth是表头可视的宽度

文章只是阐述了大概的用法,为了避免有坑,建议先看实例再去把mixins整合到自己的项目中,完整例子已上传到gitHub,地址 github.com/LSZ579/elem… ,欢迎各位大佬提bug,和修改意见,如觉得有帮助,请给一个start

核心代码


/* 组件需要提供parent字段,指定表格的className(字符串) */
const rafThrottle = (fn) => {
  let locked = false;
  return function (...args) {
    if (locked) return;
    locked = true;
    window.requestAnimationFrame(_ => {
      fn.apply(this, args);
      locked = false;
    });
  };
}
export default {
  mounted() {
    this._initSticky()
  },
  deactivated() {
    this.clearListener()
  },
  beforeDestroy() {
    this.clearListener()
    //取消监听窗口大小
    window.removeEventListener('resize', this.resizeChange)
  },
  activated() {
    this.initFixedHeader()
    this.updateFixedRight()
    window.addEventListener('resize', this.resizeChange)
    let timer
    timer = setTimeout(() => {
      let container = this.containerDom
      if (container[0].scrollTop > 0) {
        container[0].scrollTop = container[0].scrollTop + 2
      }
      clearTimeout(timer)
    }, 1000)
  },
  methods: {
    /* 初始化 */
    _initSticky() {
      this.containerDom = document.getElementsByClassName('main-container')
      this.clearListener()
      let timer = setTimeout(() => {
        this.initFixedHeader()
        clearTimeout(timer)
      }, 300)
      window.addEventListener('resize', this.resizeChange)
    },
    activatedReload() {
      window.addEventListener('resize', this.resizeChange)
      let timer = setTimeout(() => {
        this.clearFixedStyle()
        this.initFixedHeader()
      }, 300)
      this.timerList.push(timer)
    },
    // 窗口大小变化时,初始化
    resizeChange() {
      this.headerDragend()
      let timer = setTimeout(() => {
        this.initFixedHeader()
        clearTimeout(timer)
      }, 300)
    },
    async initFixedHeader() {
      if (this.parent) {
        // console.log('启动监听,页面:', this.parent)
        this.parentDom = document.getElementsByClassName(this.parent)
        if (this.parentDom && this.parentDom.length !== 0) {
          this.tableWidth = this.parentDom[0].querySelector('.el-table__header-wrapper').getBoundingClientRect().width
          this.setScrollXWidth()
          this.tableDom = this.parentDom[0].getElementsByClassName('el-table__header-wrapper')
          this.scrollDom = document.querySelector('.main-container')
          this.scrollDom.addEventListener('scroll', this.scrollEvent)
        }
      }
    },
    // 清空监听事件
    clearListener() {
      if (this.scrollDom) {
        this.scrollDom.removeEventListener('scroll', this.scrollEvent)
        window.removeEventListener('resize', this.resizeChange)
        this.clearFixedStyle()
        // console.log('卸载监听,页面:', this.parent)
        this.timerList.forEach(key => {
          clearTimeout(key)
        });
      }
    },
    // 更新右侧固定栏
    updateFixedRight() {
      let { fixedRightHeaderDom, dom } = this.getFixedDom()
      if (dom.classList.contains('fixed')) {
        let timer = setTimeout(() => {
          this.setFixedStyle({
            dom: fixedRightHeaderDom,
            left: this.fixedRightDom[0].getBoundingClientRect().left + 'px',
            width: getComputedStyle(this.fixedRightDom[0]).width,
            scrollLeft: fixedRightHeaderDom.scrollWidth
          })
          clearTimeout(timer)
        }, 100)
      }
    },
    async headerDragend() {
      await this.updateWidth()
      await this.updateFixedRight()
      this.setScrollXWidth()
      // await this.updateHeaderHeight()
    },
    setScrollXWidth() {
      let timer = setTimeout(() => {
        if (!this.parentDom) this.parentDom = document.getElementsByClassName(this.parent)
        if (this.parentDom.length == 0) return
        let dom = this.parentDom[0].querySelector('.el-table__header')
        this.tableWidth = this.parentDom[0].querySelector('.el-table__body-wrapper').getBoundingClientRect().width
        this.tableDom[0].style.width = this.tableWidth + 'px'
        this.updateHeaderHeight()
        this.headerWidth = dom.style.width
        clearTimeout(timer)
      }, 200)
    },
    // 更新表格宽度,(拖拽改变宽度时使用)
    updateWidth() {
      if (!this.parentDom) this.parentDom = document.getElementsByClassName(this.parent)
      const bodyWrapperDom = this.parentDom[0].getElementsByClassName('el-table__body-wrapper')[0]
      const width = getComputedStyle(bodyWrapperDom).width//表格宽度
      // 给表格设置宽度。
      const tableParent = this.tableDom
      for (let i = 0; i < tableParent.length; i++) {
        tableParent[i].style.width = width
      }
    },
    getFixedDom() {
      let fixedRightHeaderDom, fixedRightBox, fixedLeftHeaderDom, fixedLeftBox;
      let dom = this.tableDom[0]
      if (this.fixedLeftDom && this.fixedLeftDom[0]) {
        let lefarr = this.fixedLeftDom[0].children
        fixedLeftHeaderDom = lefarr[0]
        fixedLeftBox = lefarr[1]
      }
      if (this.fixedRightDom && this.fixedRightDom[0]) {
        let rightarr = this.fixedRightDom[0].children
        fixedRightHeaderDom = rightarr[0]
        fixedRightBox = rightarr[1]
      }
      return { fixedRightHeaderDom, fixedRightBox, fixedLeftHeaderDom, fixedLeftBox, dom }
    },
    // 更新表头高度,表头高度有可能改变
    updateHeaderHeight() {
      this.$nextTick(() => {
        this.tableDom = this.parentDom[0].getElementsByClassName('el-table__header-wrapper')
        let obj = this.tableDom[0].getBoundingClientRect()
        if (obj.height != this.tablexy.height) {
          this.tablexy.height = obj.height
          let { dom } = this.getFixedDom()
          if (dom.classList.contains('fixed')) {
            let timer = setTimeout(() => {
              this.parentDom[0].getElementsByClassName('el-table__fixed-body-wrapper')[0].style.top = 0
              let container = this.containerDom
              if (container && container[0]) {
                container[0].scrollTop = container[0].scrollTop + 3;
              }
              clearTimeout(timer)
            }, 100)
          }
        }
      })
    },
    // 获取表格属性
    getTableXy() {
      this.tablexy = this.tableDom[0].getBoundingClientRect()
      this.tablexy.height = this.tableDom[0].offsetHeight
      return this.tablexy
    },
    getDom() {
      if (!this.parentDom) {
        this.parentDom = document.getElementsByClassName(this.parent)
      }
    },
    //滚动事件
    scrollEvent: rafThrottle(async function (e) {
      this.getDom()
      this.tableDom = this.parentDom[0].getElementsByClassName('el-table__header-wrapper')
      if (this.tablexy.top == 0 || !this.tablexy.height || !this.tablexy.top) {
        await this.getTableXy()
      }
      this.fixedRightDom = this.parentDom[0].getElementsByClassName('el-table__fixed-right')
      this.fixedLeftDom = this.parentDom[0].getElementsByClassName('el-table__fixed')
      let { height, top, left } = this.tablexy
      let scrollTop = e.target.scrollTop
      let { fixedRightHeaderDom, fixedRightBox, fixedLeftHeaderDom, fixedLeftBox, dom } = this.getFixedDom()
      if (scrollTop >= height / 2 + top) {
        // 存在右侧固定表头
        if (fixedRightHeaderDom) {
          this.setFixedStyle({
            dom: fixedRightHeaderDom,
            left: this.fixedRightDom[0].getBoundingClientRect().left + 'px',
            width: getComputedStyle(this.fixedRightDom[0]).width,
            scrollLeft: fixedRightHeaderDom.scrollWidth
          })
          fixedRightBox.style.top = 0
        }
        // 左侧固定
        if (fixedLeftHeaderDom) {
          this.setFixedStyle({
            dom: fixedLeftHeaderDom,
            left: left + 'px',
            width: getComputedStyle(this.fixedLeftDom[0]).width,
            scrollLeft: 0
          })
          fixedLeftBox.style.top = 0
        }
        dom.classList.add('fixed')//加一个固定标识
        this.updateWidth()
        dom.style.position = 'fixed'
        dom.style.zIndex = '2000'
        dom.style.top = 0 + 'px'
        dom.style.overflow = 'hidden'
      } else {
        this.clearFixedStyle()
      }
    }),
    //设置固定
    setFixedStyle(data) {
      let { dom, scrollLeft, width, left } = data
      dom.style.zIndex = '2000'
      dom.style.position = 'fixed'
      dom.style.top = '0'
      dom.scrollLeft = scrollLeft
      dom.style.width = width
      dom.style.overflow = 'hidden'
      dom.style.left = left
    },
    // 重置样式
    reset() {
      this.clearFixedStyle()
    },
    // 清除header固定
    clearFixedStyle() {
      if (!this.tableDom) return
      let { height, left } = this.tablexy
      let { dom, fixedRightHeaderDom, fixedRightBox, fixedLeftHeaderDom, fixedLeftBox } = this.getFixedDom()
      if (dom.classList.contains('fixed')) {
        if (fixedRightHeaderDom) {
          fixedRightBox.style.top = height + 'px'
          fixedRightHeaderDom.removeAttribute("style");
        }
        if (fixedLeftHeaderDom) {
          fixedLeftHeaderDom.style.zIndex = '0'
          fixedLeftHeaderDom.style.position = 'static'
          fixedLeftHeaderDom.style.top = 0 + 'px'
          fixedLeftHeaderDom.style.left = left + 'px'
          fixedLeftBox.style.top = getComputedStyle(dom).height
        }
        dom.classList.remove('fixed')
        dom.style.position = 'static'
        dom.style.top = '0'
        dom.style.zIndex = '0'
      }
    },
  },
  computed: {
    __opened() {
      return this.$store.state.app.sidebar.opened
    }
  },
  watch: {
    __opened() {
      this.$nextTick(() => {
        this.setScrollXWidth()
      })
    }
  },
  data() {
    return {
      tablexy: {},//表格的左边宽度信息
      fixedRightDom: null,//右侧
      fixedLeftDom: null,//左侧栏固定
      scrollDom: null,//滚动的dom
      parentDom: null,//表格的父元素dom
      tableWidth: 0,
      timerList: [],
      tableDom: null,
      containerDom: null
    }
  },
}