element plus + vue3,table组件横向滚动条粘性贴底

2,879 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

需求背景:

  el-table组件的横向滚动条默认是放在table元素的最底下,当table高度足够高的时候,滚动条的位置会低于浏览器窗口。这时如果想要进行横向滚动就需要将浏览器窗口滚到下面。在进行横向滚动之后想要看的数据又在列表的第一条。这时有需要回到上面重新查看。这一来一回的操作显得有些冗余,效率不高。
  最终出现在这个需求,希望滚动条对于table表格粘性贴底。

方案分析:

  本需求使用到的环境是:element plus,vue3。

  搜索之后有找到几个解决方案,但是都不太适用于本需求的场景 例如:

  1. 手写一个滚动条绑定table滚动条,然后手写的滚动条吸底(经实践,滚动条的绑定机制过于繁琐并且不方便进行大范围使用);
  2. 第三方组件el-table-horizontal-scroll(经实践该组件只支持vue2);
  3. el-table提供的max-height(实际页面情况很难计算max-height实际应该取什么值)

最终经研究得出的方案:

  在DOM元素加载完成的时候,“剪取”el-table内的滚动条DOM元素模块,“粘贴”到el-table层级下(原本层级在el-table下的多个子层级)然后使它对于el-table元素进行粘性布局。
  position: sticky的使用是有一定要求的。它的基本逻辑是,使用了position: sticky的元素,会向上寻找最近的滚动组件并根据它进行固定。滚动条的元素“剪取”到el-table元素的直属子层级,方便让滚动条元素“固定”到table上。
  如果它中途的父级元素存在,overflow:hidden/scroll/auto/overlay时,sticky的行为将会被抑制。得不到页面效果
  粘性布局的使用需要基础逻辑demo:

<template>
  <el-table
    ref="tableRef"
    :data="tableData"
    style="width: 100%"
    border
    scrollbar-always-on
  >
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
  </el-table>
</template>

<script>
import { reactive, toRefs, onMounted, ref } from "vue";
export default {
  name: "TableList",
  setup() {
    let state = reactive({
      tableRef: ref(),
    });
    
    const tableData = new Array(50).fill({ date: '2022-08-23', name: "pinklin" });
    
    onMounted(() => {
      if (state?.tableRef) {
        const bar = state.tableRef.$el?.querySelector(".el-scrollbar__bar");
        state.tableRef.$el.appendChild(bar);
      }
    });
    
    return {
      ...toRefs(state),
      tableData,
    };
  },
  computed: {},
};
</script>

<style>
.el-scrollbar__bar {
  z-index: 11;
  overflow-x: hidden;
  position: sticky;
}
.el-table.el-table--border {
  overflow: unset;
}
</style>

  为了方便使用。我们把它封装到vue3的指令里面。使用的时候只需要将其引入,然后在el-table上使用v-scrollbar即可
  具体指令的实现如下:

/**
* v-scrollbar
* 放在el-table 标签上 使其底部滚动条吸底
* 接收参数:Boolean
* v-scrollbar="true"
*/
import type { Directive, DirectiveBinding } from 'vue';

const directive: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    if (binding.value) {
      const bar = el?.querySelector('.el-scrollbar__bar');
      if (bar && el) {
        bar?.classList?.add('v-scroll__el-scrollbar__bar');
        el?.appendChild(bar);
      }
    }
  },
};

export default directive;

同时,把css样式设置到全局,或者放到使用到该指令的地方。

.v-scroll__el-scrollbar__bar.el-scrollbar__bar{
  z-index: 11;
  overflow-x: hidden;
  position: sticky;
}

.el-table.el-table--border{
  overflow: unset;
}

最终实现的效果大致如下如图:

image.png