使用React+ant Table 实现 表格无限循环滚动播放

数据大屏表格数据,当表格内容超出(出现滚动条)时,无限循环滚动播放,鼠标移入暂停滚动,鼠标移除继续滚动;数据量小没有超出时不需要滚动。

*使用时应注意,滚动区域高度=父元素高度 - 表头高度

1、组件内容

tsx 复制代码
import React, { useState, useEffect, useRef } from "react";
import { Table } from "antd";
import { ColumnsType, TableRef } from "antd/lib/table";
import styles from "./styles.less";
import Nodate from "../Other/nodata";

interface InfiniteScrollTableProps<T> {
  /** 表格数据源 */
  dataSource: T[];
  /** 表格列定义 */
  columns: ColumnsType<T>;
  /**
   * 唯一字段
   */
  rowKeyField: string;
  /**
   * 滚动速率。
   * @default 0.5
   * @description 建议在 0.5-3 之间调整
   * */
  speed?: number;
}

/**
 * @description 无限循环滚动table
 */
const InfiniteScrollTable = <T = any,>(props: InfiniteScrollTableProps<T>) => {
  const { dataSource, columns, speed = 0.5, rowKeyField = "key" } = props;
  const [doubleData, setDoubleData] = useState<any[]>([]);
  const tableRef = useRef<TableRef>(null);
  const animationRef = useRef<number | null>(null);
  const isHovered = useRef(false);
  // 滚动高度
  const scrollHeight = useRef(0);

  // 滚动动画
  const startScrolling = (begin: boolean) => {
    if (isHovered.current || !tableRef.current || !tableHasScroll()) return;

    const table = tableRef.current.nativeElement;
    const wrapper = table.querySelector(".ant-table-body");
    if (!wrapper) {
      return;
    }
    // 重置滚动位置
    if (begin) {
      wrapper.scrollTop = 0;
    }

    const scroll = () => {
      if (isHovered.current) return;

      // 滚动到底部时重置位置
      if (wrapper.scrollTop >= wrapper.scrollHeight / 2) {
        wrapper.scrollTop = 0;
      } else {
        wrapper.scrollTop += speed;
      }

      animationRef.current = requestAnimationFrame(scroll);
    };

    animationRef.current = requestAnimationFrame(scroll);
  };

  // 表格内容是否出现滚动
  const tableHasScroll = () => {
    const table = tableRef.current?.nativeElement;
    const wrapper = table?.querySelector(".ant-table-body");
    if (!wrapper) {
      return false;
    }
    const hasScroll = wrapper.scrollHeight > wrapper.clientHeight;
    return hasScroll;
  };

  // 停止滚动
  const stopScrolling = () => {
    if (animationRef.current) {
      cancelAnimationFrame(animationRef.current);
      animationRef.current = null;
    }
  };

  // 处理鼠标事件
  const handleMouseEnter = () => {
    isHovered.current = true;
    stopScrolling();
  };

  const handleMouseLeave = () => {
    isHovered.current = false;
    startScrolling(false);
  };

  useEffect(() => {
    // 先设置为初始数据
    setDoubleData([...dataSource]);
  }, [dataSource]);

  // 开始滚动
  useEffect(() => {
    // 创建两倍数据用于实现无缝滚动
    if (tableHasScroll() && doubleData.length === dataSource.length) {
      setDoubleData([...dataSource, ...dataSource]);
    }
    startScrolling(true);
    return () => stopScrolling();
  }, [tableRef.current, doubleData]);

  return (
    <div
      ref={(el) => (scrollHeight.current = el?.clientHeight || 0)}
      className={styles["infinite-scroll-table"]}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <Table
        ref={tableRef}
        columns={columns}
        dataSource={doubleData}
        pagination={false}
        scroll={{ y: scrollHeight.current - 57 }}
        rowClassName={(record, index) =>
          index % 2 === 0 ? styles["even-row"] : styles["odd-row"]
        }
        rowKey={(record: any, index) => (record?.[rowKeyField] ?? "") + index}
      />
    </div>
  );
};

export default InfiniteScrollTable;

2、样式

less 复制代码
.infinite-scroll-table {
    position: relative;
    height: 100%;
    transition: all 0.3s ease;
    border: 1px solid rgba(187,187,187,1);

    .highlight {
        color: #40a9ff;
        font-weight: 600;
    }

    .even-row {
        background: rgba(255,255,255);
        height: 60px;
    }
    
    .odd-row {
        background: rgba(250,250,250);
        height: 60px;
    }

    :global {
        .ant-table-header{
            border-radius: 0;
        }
        
        .ant-table-thead > tr > th {
            background: rgba(242,242,242) !important;
            color: #333 !important;
            font-size: 14px;
            font-weight: 600;
            text-align: center;
            border-start-start-radius: 0 !important;
            border-start-end-radius: 0 !important;
        }
        
        .ant-table-body {
            scrollbar-width: none;
            -ms-overflow-style: none;
        }
        
        .ant-table-cell{
            font-weight: normal;
            font-size: 14px;
        }
        
        .ant-table-body::-webkit-scrollbar {
            display: none;
        }
        
        .ant-table-row:hover > td {
            background: rgba(64, 144, 255, 0.2) !important;
        }

        .ant-table-placeholder .ant-table-cell{
            border: none;
        }

    }
    
}
相关推荐
Sunlightʊə2 小时前
2.登录页测试用例
运维·服务器·前端·功能测试·单元测试
Code Crafter3 小时前
ES6-ES14 新特性速查
前端·ecmascript·es6
Lhuu(重开版3 小时前
CSS从0到1
前端·css·tensorflow
CDwenhuohuo3 小时前
微信小程序里用 setData() 修改数据并打印输出 的几种写法
javascript·微信小程序·小程序
不说别的就是很菜4 小时前
【前端面试】HTML篇
前端·html
前端一小卒4 小时前
生产环境Sourcemap策略:从苹果事故看前端构建安全架构设计
前端·javascript
im_AMBER4 小时前
React 18
前端·javascript·笔记·学习·react.js·前端框架
老前端的功夫4 小时前
Vue2中key的深度解析:Diff算法的性能优化之道
前端·javascript·vue.js·算法·性能优化
集成显卡5 小时前
AI取名大师 | PM2 部署 Bun.js 应用及配置 Let‘s Encrypt 免费 HTTPS 证书
开发语言·javascript·人工智能
han_5 小时前
前端高频面试题之Vue(高级篇)
前端·vue.js·面试