數據大屏表格數據,當表格內容超出(出現滾動條)時,無限循環滾動播放,鼠標移入暫停滾動,鼠標移除繼續滾動;數據量小沒有超出時不需要滾動。
*使用時應注意,滾動區域高度=父元素高度 - 表頭高度
1、組件內容
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 (<divref={(el) => (scrollHeight.current = el?.clientHeight || 0)}className={styles["infinite-scroll-table"]}onMouseEnter={handleMouseEnter}onMouseLeave={handleMouseLeave}><Tableref={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、樣式
.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;}}}