table固定表头固定列实现横向纵向滚动

大致思路是:建立两个table,一个table里只有thead,一个table里只有tbody,分别把两个table用div包裹起来,设置有tbody的div固定高度,超出overflow:scroll;至于横向滚动的问题,可以设置position: sticky,然后根据左右浮动的个数及对应列所在的index,计算left或者right的长度。

一.为什么要把table拆成两部分?

因为table里thead和tbody本身无法设置高度,超出用stroll这种方式,无效。所以考虑用div来包裹,然后设置高度超出stroll,因为要实现表头固定,body滚动,所以把thead单独提出来作为一个表格,然后用定位的方式并成一个完整的表格。

<div css={select().background("#fff").height("calc(100% - 80px)").position("relative").paddingTop(38)}>    // 外部div设置padding高度为表头div的高度,表头div绝对定位,固定在顶部
//   表头table
      <div
        css={
          {
            height: 38,
            width: "100%",
            position: "absolute",
            overflowX: "scroll",
            overflowY: "hidden",
            top: 0,
            "&::-webkit-scrollbar": { display: "none" },
          } as any
        }>
        <table
          css={select()
            .width("100%")
            .position("absolute")
            .top(0)
            .overflowX("scroll")
            .borderCollapse("collapse")
            .tableLayout(tableLayout || "auto")}
          {...otherProps}>
          <thead>
            <tr css={select().backgroundColor(colors.gray1).borderSpacing(0)}>
              {map(endColumns, (column, index) => (
                <th
                  key={column.key}
                  css={select()
                    .paddingY(roundedEm(0.7))
                    .paddingX(roundedEm(0.9))
                    .width(column.width || "auto")
                    .textAlign(column.align || "left")
                    .fontWeight(500)
                    .fontSize(theme.fontSizes.s)}>
                  { column.title }
                </th>
              ))}
            </tr>
          </thead>
        </table>
      </div>
//   表体table
      <div
        css={
          {
            maxHeight: "100%",    // 可以自己指定表体高度
            clear: "both",
            overflow: "scroll"
          } as any
        }>
        <table
          css={select()
            .width("100%")
            .borderCollapse("collapse")
            .tableLayout('fixed')}
          {...otherProps}>
          <tbody
            css={select()
              .color(colors.gray6)
              .with(
                select("tr").borderBottom(`1px solid`).borderColor(theme.state.borderColor).borderSpacing(0)
              )
              .with(select("tr:last-child").borderBottom(`none`))
              .with(select("tr td").padding(roundedEm(0.9)))}>
            //   这里是tbody里数据的处理,类似antd的
            {map(dataSource, (row, rowIdx) => (
              <TableRow
                key={get(row, rowKey)}
                columns={endColumns}
                expandable={expandable}
                row={row}
                rowIndex={rowIdx}
                onRowClick={onRowClick}
                rowStyle={rowStyle}
              />
            ))}
          </tbody>
        </table>
      </div>

      {loading ? <Loading /> : size(dataSource) ? null : <Empty />}   // 这里做的是数据为空和请求数据是的样式处理
    </div>

二.position:sticky是什么?

sticky是position的新属性值,叫黏性定位。它是一个在static和fixed变化的属性,当你的内容位置没有超过容器范围时,它是正常布局,你设置的定位属性(left,right等)是无效的;当你的内容位置超出了容器的范围时,它会变成fixed定位,定位位置根据你设置的left,right的值来定位。
了解了sticky的用法,自然就知道怎么来实现固定列横向滚动表格了。这里贴一个thead表格的横向滚动写法,tbody的表格是一样的。

<thead>
            <tr css={select().backgroundColor(colors.gray1).borderSpacing(0)}>
              {map(endColumns, (column, index) => (
                <th
                  key={column.key}
                  style={{
                    [column.sticky as string]: getStickyOffset(index, columns, column.sticky),     // 根据column传过来的左固定还是右固定来计算对应的left或right的值
                  }}
                  css={select()
                    .paddingY(roundedEm(0.7))
                    .paddingX(roundedEm(0.9))
                    .width(column.width || "auto")
                    .textAlign(column.align || "left")
                    .fontWeight(500)
                    .fontSize(theme.fontSizes.s)
                    //   根据column传过来的是否sticky来设置position属性
                    .with(column.sticky ? stickyColumnStyle(column.sticky) : null)}>
                  {column.onSort ? (
                    <div css={{ display: "flex", justifyContent: "flex-start", alignItems: "center" }}>
                      {column.title}
                      {column.onSort && <SortTrigger onSort={column.onSort} />}
                    </div>
                  ) : (
                    column.title
                  )}
                </th>
              ))}
            </tr>
          </thead>

// column数据格式
const columns = [
    {
      title: "产品名称",
      key: "productName",
      width: 200,
      ellipsis: true,
      sticky: "left",  // 左侧固定
      formatter: (_: string) => (_ ? _ : "-"),
    },
    {
      title: "产品厂商",
      key: "manufacturerName",
      width: 200,
      ellipsis: true,
      formatter: (_: any) => (_ ? _ : "-"),
    },
    {
      title: "型号",
      key: "productModel",
      width: 150,
      ellipsis: true,
      formatter: (_: any) => (_ ? _ : "-"),
    },
    {
      title: "价格",
      key: "productPrice",
      width: 100,
      ellipsis: true,
      formatter: (_: number) => (_ || _ === 0 ? `¥${_}` : "-"),
    },
    {
      title: "操作",
      key: "action",
      width: 100,
      sticky: "right",  // 右侧固定
      align: "right",
      formatter: (_: any, record: any) => (
        <span
            css={{ color: "#4F78E0", cursor: "pointer" }}>
            编辑
          </span>
      ),
    },
  ];

// getStickyOffset
const getStickyOffset = (currentIndex: number, endColumns: ITableColumn<any>[], type: string | undefined) => {
  if (currentIndex === 0) {
    return 0;
  }

  let max = 0;
  let value = 0;

  forEach(endColumns, (item, index) => {
    if (currentIndex === index) {
      return;
    }
    max += Number(item.width);
    if (currentIndex > index) {
      value += Number(item.width);
    }
  });

  if (type === "right") {
    return max - value;
  }
  return value;
};

// stickyColumnStyle
const stickyColumnStyle = (type: "left" | "right") =>
  select()
    .position("sticky")
    // .zIndex(2)
    .background("inherit")
    .with(
      select("&:after")
        .content(`""`)
        .position("absolute")
        .height("100%")
        .width("30px")
        .top(0)
        .transform("translate(100%)")
        .transition("box-shadow 0.3s")
        .pointerEvents("none")
        // 可以设置固定后的样式,添加阴影之类的
        .with(
          type === "left"
            ? // ? select().right(0).transform(`translateX(100%)`).boxShadow(`inset 10px 0 8px -8px rgb(0 0 0 / 15%)`)
              select().right(0).transform(`translateX(100%)`)
            : // : select().left(0).transform(`translateX(-100%)`).boxShadow(`inset -10px 0 8px -8px rgb(0 0 0 / 15%)`),
              select().left(0).transform(`translateX(-100%)`),
        ),
    );

三.怎么实现表头和表体同步滚动

实现了竖直滚动表头固定,表头和表体可以横向滚动,但是遇到一个问题:表头和表体的滚动是分开的,各滚各的,显然不是我们要的效果,所以,需要实现表头和表体的同步滚动,这里需要用到scroll事件。
在react函数组件里,要操作组件内的dom元素,需要用到useRef来获取dom的实例。

// useRef分别获取thead和tbody两个div的实例
const headerRef = useRef<HTMLDivElement>(null);
  const bodyRef = useRef<HTMLDivElement>(null);
  return (
    <div css={select().background("#fff").height("calc(100% - 80px)").position("relative").paddingTop(38)}>
      <div
        ref={headerRef}
      // 通过赋值scrollLeft,表头移动时带动表体移动
        onScroll={() => {
          // console.log("headerRef", headerRef.current.scrollLeft);
          bodyRef.current.scrollLeft = headerRef.current.scrollLeft;
          // console.log("bodyRef", bodyRef);
        }}
        css={
          {...} as any
        }>
        <table>
          <thead>
            ...
          </thead>
        </table>
      </div>
      <div
        css={
          {
            maxHeight: "100%",
            clear: "both",
            overflow: "scroll"
          } as any
        }
        ref={bodyRef}
        onScroll={() => {
          // console.log("headerRef", headerRef.current.scrollLeft);
          headerRef.current.scrollLeft = bodyRef.current.scrollLeft;
          // console.log("bodyRef", bodyRef);
        }}>
        <table>
          <tbody>
            ...
          </tbody>
        </table>
      </div>
        ...
    </div>
  );

以上,就实现了一个简易的固定表头固定列横向滚动纵向滚动的表格。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容