数据大屏表格数据,当表格内容超出(出现滚动条)时,无限循环滚动播放,鼠标移入暂停滚动,鼠标移除继续滚动;数据量小没有超出时不需要滚动。
*使用时应注意,滚动区域高度=父元素高度 - 表头高度
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 (
<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、样式
.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;
}
}
}