Skip to content

el-table表头自动吸顶

1175字约4分钟

ElementUI表格吸顶

2024-05-16

背景

在使用el-table的时候,在数据多的情况下滚动表格会看不到表头是什么,可以使用高度自适应,这是对表格高度的限制同时会出现滚动条,这样用户体验不好,所以考虑不设置高度通过表头吸顶来实现。

思路

网上也有一些解决方案写了一大堆代码。很麻烦。(os:我没成功过)

我研究了el-tabledom结构,发现其实只需要少量简单的计算去修改样式就可以达到效果。

主要还是使用CSS属性 position: sticky

注意

由于使用的是sticky,所以要保证祖先元素不能设置overflow:hidden这类的样式,否则不生效。

效果

实现

为了方便使用,把表头吸顶写成一个方法,最后在指令里调用即可。

  1. 吸顶高度确认

指令什么都不传默认吸顶高度为0,可以传递具体的吸顶高度值(数字),可以传递可以获取到dom的选择器名

const stickyThead = (el, binging, vnode) => {
	let top = "0px";
    //如果是吸顶高度
    if (!isNaN(Number(binging.value))) {
    	top = binging.value + "px";
  	}
    //如果是选择器名
    if (typeof binging.value === "string") {
    	let rect = document.querySelector(binging.value)?.getBoundingClientRect();
    	top = rect ? rect.top + rect.height + "px" : "0px";
  	}
}
  1. 头部吸顶
el.style.overflow = "visible";
const tHeader = el.querySelector(".el-table__header-wrapper");
tHeader.style.position = "sticky";
tHeader.style.top = top;
tHeader.style.zIndex = 10;

到这里基本上已经实现表头吸顶的功能了,但是如果出现固定列的话就不行。那么继续完善。

研究表格元素结构发现,没有固定列的头部在el-table__header-wrapper,而有固定列的头部会被额外拆分到el-table__fixedel-table__fixed-right, 其实可以直接修改el-table__header-wrapper里面的th样式即可。

  1. 去除is-hidden

需要把th中的is-hidden这个类名删除,才会显示固定列的头

const ths = tHeader.querySelectorAll("th.is-hidden");
ths.forEach((item) => {
	item.classList.remove("is-hidden");
});
  1. 固定列吸顶
// 找到实例
const table = vnode.context.$children.find((item) => item.$el === el);
// 找到左边固定列的信息
const leftFixed = table.fixedColumns;
if (leftFixed && leftFixed.length) {
	let leftFixedWidth = 0;
	leftFixed.forEach((item) => {
		let itemW = item.width || item.realWidth || item.minWidth;
		const cell = tHeader.querySelector("th." + item.id);
		if (cell) {
			cell.style.position = "sticky";
			cell.style.left = leftFixedWidth + "px";
			cell.style.top = top;
			cell.style.zIndex = 10;
			leftFixedWidth += itemW;
		}
	});
}

// 找到右边固定列
const rightFixed = table.rightFixedColumns;
if (rightFixed && rightFixed.length) {
	let rightFixedWidth = 0;
	for (let i = rightFixed.length - 1; i >= 0; i--) {
		let itemW = rightFixed[i].width || rightFixed[i].realWidth || rightFixed[i].minWidth;
      const cell = tHeader.querySelector("th." + rightFixed[i].id);
      if (cell) {
        cell.style.position = "sticky";
        cell.style.right = rightFixedWidth + "px";
        cell.style.top = top;
        cell.style.zIndex = 10;
        rightFixedWidth += itemW;
      }
    }
  }
};

细节: 右边的固定列要倒着设置

  1. 指令调用时机
var tableOb = null;
var domOb = null;
export default {
  inserted(el, binging, vnode) {
    //监听表格变化
    tableOb = new ResizeObserver(() => {
      stickyThead(el, binging, vnode);
    });
    tableOb.observe(el);

    // 监听目标dom变化
    if (typeof binging.value === "string") {
      let isDom = document.querySelector(binging.value);
      if (isDom) {
        domOb = new ResizeObserver(() => {
          stickyThead(el, binging, vnode);
        });
        domOb.observe(isDom);
      }
    }
  },
  unbind(el, binging, vnode) {
    tableOb && tableOb.unobserve(el);
    if (typeof binging.value === "string") {
      let isDom = document.querySelector(binging.value);
      if (isDom) {
        domOb && domOb.unobserve(el);
      }
    }
  },
};
  • 思考一:表格监听

表格插入到页面的时候要监听表格的变化,从而调用这个方法,这个监听是必要的,能够在表格变化的时候重新计算位置。(考虑左右位置的计算)

  • 思考二:目标元素监听

在使用目标元素来决定吸顶高度时,随着页面的变化可能目标元素的高度会变高,那就有必要重新调用这个方法,如果高度固定就不需要监听;或者目标元素的宽小于等于表格的宽度,这样页面变化会触发表格的监听同样不需要这个监听。所以这个监听不是必要的。主要关注点还是在stickyThead这个方法上,剩下的就是触发时机。(考虑吸顶的位置)

优化

通过修饰符来决定执行的方法

  • fixed

fixed修饰符表示当前表格有固定列需要使用表格监听,没有的话就可以使用普通的表头吸顶,减少性能消耗

 if (binging.modifiers.fixed) {
	// 监听表格变化
	tableOb = new ResizeObserver(
		debounce(() => {
          stickyThead(el, binging, vnode)
        }, 500)
      )
      tableOb.observe(el)
    } else {
      sticky(el, binging, vnode)
}

// 简易吸顶
const sticky = (el, binging, vnode) => {
  let top = '0px'
  if (!isNaN(Number(binging.value))) {
    top = binging.value + 'px'
  }
  if (typeof binging.value === 'string') {
    const rect = document.querySelector(binging.value)?.getBoundingClientRect()
    top = rect ? rect.top + rect.height + 'px' : '0px'
  }

  el.style.overflow = 'visible'
  const tHeader = el.querySelector('.el-table__header-wrapper')
  tHeader.style.position = 'sticky'
  tHeader.style.top = top
  tHeader.style.zIndex = 10
}
  • dom

dom修饰符表示如果传入选择器可以根据实际情况使用,正如上面思考二所说如果获取的dom高度会变化,导致表头吸顶位置不对,可以使用这个修饰符

 if (binging.modifiers.dom) {
      // 监听目标dom变化
      if (typeof binging.value === 'string') {
        const isDom = document.querySelector(binging.value)
        if (isDom) {
          domOb = new ResizeObserver(
            debounce(() => {
              stickyThead(el, binging, vnode)
            }, 500)
          )
          domOb.observe(isDom)
        }
      }
}

书洞笔记