实现react表格自滚动轮播功能

说到底还是需求啦~┓( ´∀` )┏

需求:就是一个表格,可以自滚动轮播,之前做过用css实现简单的表格自滚动,但是这个表格另外的需求,鼠标hover的时候,滚动停止,鼠标移开的时候滚动继续,还要有hover状态和点击事件,感觉之前用css来做滚动已经没法满足了。(:з」∠) 所以就着手重新搞一个了。

image-20201217161646344

思路:

  • 因为表格头部是固定不变的,只是表格的body进行滚动,所以把表格头部和滚动的部分分为两个表格
  • 利用scrollTop来实现滚动
  • 利用setInterval做轮询滚动功能
  • 利用onMouseEnteronMouseLeave事件来做hover监听

问题:

  • 因为是两个表格,要解决不同表格里文字对齐问题
  • 要解决scrollTop到最底部的问题
  • 要解决表格滚动时候衔接问题

嘿呀,本来还想写一波问题解决思路,但还是直接上代码吧哈哈哈哈(肯定不是因为我懒了)


 
import React, { CSSProperties, ReactNode, useCallback, useEffect, useRef } from 'react';
import './customTable.scss';
import { useImmer } from 'use-immer';

interface PropsColumn {
  dataIndex: string;
  title: string;
  width?: number;
  render?: (data: any) => ReactNode;
}

interface Props {
  uId: string;
  column?: PropsColumn[];
  data?: any[];
  autoScroll?: boolean;
  scrollSpeed?: number;
  height: number;
  thFont?: number;
  tbFont?: number;
  isRowSelect?: boolean;
  rowSelectEvent?: (data: any, index: number) => void;
  style?: CSSProperties;
}

const MyMar: object = {};
const speed = 50;

const CustomTable = ({
  uId,
  column = [],
  data = [],
  autoScroll = true,
  scrollSpeed,
  height = 0,
  thFont = 16,
  tbFont = 16,
  isRowSelect = false,
  rowSelectEvent,
  style,
}: Props) => {
  const tableRef = useRef<HTMLDivElement>(null);
  const tableRef1 = useRef<any>(null);
  const tableRef2 = useRef<HTMLDivElement>(null);

  const [state, setState] = useImmer({
    columnWidth: [] as number[],
    selected: '',
    tableData: data.map((item) => {
      return { rowSelect: false, ...item };
    }),
  } as any);

  const Marquee = () => {
    if (tableRef2.current && tableRef.current && tableRef1.current) {
      // if (tableRef2.current.offsetTop - tableRef.current.scrollTop <= 0) {
      if (tableRef2.current.scrollHeight - tableRef.current.scrollTop <= 0) {
        tableRef.current.scrollTop -= tableRef1.current.offsetHeight;
      } else {
        tableRef.current.scrollTop += 1;
      }
    }
  };

  const handleOnMouseOver = () => {
    clearInterval(MyMar[uId]);
    MyMar[uId] = undefined;
  };

  const handleOnMouseOut = () => {
    if (!MyMar[uId] && autoScroll) MyMar[uId] = setInterval(Marquee, scrollSpeed || speed);
  };

  const onclickEvent = (value: any, index: number) => {
    if (isRowSelect) {
      setState((pre) => {
        if (pre.selected !== '') {
          pre.tableData[state.selected].rowSelect = undefined;
        }
        pre.tableData[index].rowSelect = true;
        pre.selected = index;
      });
    }
    if (isRowSelect && rowSelectEvent) {
      const item = { ...value };
      delete item.rowSelect;
      rowSelectEvent(item, index);
    }
  };

  // 处理表格头部
  const handleTHeader = useCallback(() => {
    setTimeout(() => {
      if (
        tableRef1.current &&
        tableRef1.current.children[0] &&
        tableRef1.current.children[0].rows[0] &&
        tableRef1.current.children[0].rows[0].cells
      ) {
        let width = [] as number[];
        const elements: any = Array.from(tableRef1.current.children[0].rows[0].cells);
        width = elements.map((item) => {
          return item.offsetWidth;
        });

        setState((pre) => {
          pre.columnWidth = width;
        });
      }
    }, 800);
  }, []);

  useEffect(() => {
    if (autoScroll) {
      if (tableRef2.current) {
        tableRef2.current.innerHTML = '';
      }
      if (tableRef.current && tableRef1.current) {
        tableRef.current.scrollTop -= tableRef1.current.offsetHeight;
      }
      handleOnMouseOver();
      setTimeout(() => {
        if (
          tableRef2.current &&
          tableRef1.current &&
          tableRef.current &&
          tableRef.current.scrollHeight > tableRef.current.clientHeight
        ) {
          tableRef2.current.innerHTML = tableRef1.current.innerHTML;
        }
      }, 300);
      setTimeout(() => {
        handleOnMouseOut();
      }, 1000);
    }
    setState((pre) => {
      pre.tableData = data.map((item) => {
        return { rowSelect: false, ...item };
      });
    });
  }, [JSON.stringify(data)]);

  useEffect(() => {
    if (autoScroll) {
      setTimeout(() => {
        if (
          tableRef2.current &&
          tableRef1.current &&
          tableRef.current &&
          tableRef.current.scrollHeight > tableRef.current.clientHeight
        ) {
          tableRef2.current.innerHTML = tableRef1.current.innerHTML;
          if (!MyMar[uId]) MyMar[uId] = setInterval(Marquee, scrollSpeed || speed);
        }
      }, 2500);
    }

    handleTHeader();

    return handleOnMouseOver;
  }, [handleTHeader, uId, autoScroll]);

  return (
    <div className="jsc-scroll-table" style={{ height, ...style }}>
      <table cellSpacing="0" cellPadding="0">
        <thead>
          <tr>
            {column.map((item, index) => {
              return (
                <td
                  key={item.title}
                  style={{
                    width: item.width || state.columnWidth[index],
                    fontSize: `${thFont}px`,
                  }}
                >
                  {item.title}
                </td>
              );
            })}
          </tr>
        </thead>
      </table>
      <div
        ref={tableRef}
        id={`${uId}-table1`}
        style={{
          overflowY: autoScroll ? 'hidden' : 'scroll',
          height: height ? height - 40 : '100%',
        }}
        onMouseEnter={handleOnMouseOver}
        onMouseLeave={handleOnMouseOut}
        onFocus={() => 0}
        onBlur={() => 0}
      >
        <div id={`${uId}-tbody1`} ref={tableRef1}>
          <table cellSpacing="0" cellPadding="0">
            <tbody>
              {state.tableData.map((value, index) => {
                return (
                  <tr
                    key={column ? `tr${column[value]}${index}` : `tr${index}`}
                    className={value.rowSelect ? 'row-selected' : ''}
                    style={isRowSelect ? { cursor: 'pointer' } : { cursor: 'default' }}
                    onClick={() => {
                      onclickEvent(value, index);
                    }}
                  >
                    {column.map((col) => {
                      if (value[col.dataIndex]) {
                        return col.render ? (
                          <td
                            key={`td-${col.dataIndex}-${value[col.dataIndex]}-${index}`}
                            style={{ width: col.width, fontSize: `${tbFont}px` }}
                          >
                            {col.render(value)}
                          </td>
                        ) : (
                          <td
                            key={`td-${col.dataIndex}-${value[col.dataIndex]}-${index}`}
                            style={{ width: col.width, fontSize: `${tbFont}px` }}
                          >
                            {value[col.dataIndex]}
                          </td>
                        );
                      }
                      return col.render ? (
                        <td
                          key={`td-${col.dataIndex}-${index}`}
                          style={{ width: col.width, fontSize: `${tbFont}px` }}
                        >
                          {col.render(value)}
                        </td>
                      ) : (
                        <td style={{ width: col.width, fontSize: `${tbFont}px` }} key={index} />
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
        <div id={`${uId}-tbody2`} ref={tableRef2} />
      </div>
    </div>
  );
};

export default React.memo(CustomTable);