记录,以防忘记
SeamlessScroll.tsx
import React, { useEffect, useRef, useState } from 'react';
interface SeamlessScrollProps {
children: React.ReactNode;
speed?: number; // 滚动速度,单位:像素/秒
minItems?: number; // 最小项目数量,默认3个
className?: string;
style?: React.CSSProperties;
}
/**
* 无缝滚动组件(从下往上滚动)
* @param children - 需要滚动的内容
* @param speed - 滚动速度,默认 50 像素/秒
* @param minItems - 最小项目数量,默认3个,只有超过这个数量才会滚动
* @param className - 自定义类名
* @param style - 自定义样式
*/
const SeamlessScroll = ({ children, speed = 50, minItems = 3, className = '', style = {} }: SeamlessScrollProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [isHovered, setIsHovered] = useState(false);
const animationFrameRef = useRef<number>();
const lastTimeRef = useRef<number>(0);
const positionRef = useRef<number>(0);
const [shouldScroll, setShouldScroll] = useState(false);
// 检查是否需要滚动
useEffect(() => {
if (contentRef.current) {
const itemCount = React.Children.count(children);
setShouldScroll(itemCount > minItems);
}
}, [children, minItems]);
// 计算滚动距离
const calculateScrollDistance = (deltaTime: number) => {
return (speed * deltaTime) / 1000; // 将速度转换为每毫秒的滚动距离
};
// 处理滚动动画
const handleScroll = (timestamp: number) => {
if (!containerRef.current || !contentRef.current || !shouldScroll) return;
const deltaTime = timestamp - lastTimeRef.current;
lastTimeRef.current = timestamp;
const scrollDistance = calculateScrollDistance(deltaTime);
const content = contentRef.current;
// 更新位置
positionRef.current += scrollDistance;
// 当滚动到一半时重置位置
if (positionRef.current >= content.offsetHeight / 2) {
positionRef.current = 0;
}
// 应用变换
content.style.transform = `translateY(-${positionRef.current}px)`;
if (!isHovered) {
animationFrameRef.current = requestAnimationFrame(handleScroll);
}
};
// 开始滚动
useEffect(() => {
if (!isHovered && shouldScroll) {
lastTimeRef.current = performance.now();
animationFrameRef.current = requestAnimationFrame(handleScroll);
}
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [isHovered, speed, shouldScroll]);
// 复制内容以实现无缝滚动
const duplicatedContent = (
<div
ref={contentRef}
style={{ display: 'block', width: '100%', willChange: 'transform', transition: 'none' }} >
{children}
{shouldScroll && children}
</div>
);
return (
<div
ref={containerRef}
className={`seamless-scroll ${className}`}
style={{
position: 'relative',
overflow: 'hidden',
...style,
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{duplicatedContent}
</div>
);
};
export default SeamlessScroll;
使用
<SeamlessScroll speed={30} minItems={4} style={{ height: '700px' }}>
<div style={{ padding: '10px' }}>
<List dataSource={data} renderItem={(item) => (
<List.Item>
<Card style={{ width: '100%' }}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</Card>
</List.Item>
)}
/>
</div>
</SeamlessScroll>