新增SeamlessScroll无缝滚动组件
parent
2746cca0c3
commit
5e6f280dfb
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { ForwardRefExoticComponent, RefAttributes } from "react";
|
||||||
|
|
||||||
|
export interface SeamlessScrollProps {
|
||||||
|
/** 是否开启自动滚动,默认 true */
|
||||||
|
value?: boolean;
|
||||||
|
/** 原始数据列表 */
|
||||||
|
list: unknown[];
|
||||||
|
/** 步进速度,step 需是单步大小的约数,值越大滚动的越快,默认 1 */
|
||||||
|
step?: number;
|
||||||
|
/** 开启滚动的数据量,默认 3 */
|
||||||
|
limitScrollNum?: number;
|
||||||
|
/** 是否开启鼠标悬停,默认 true */
|
||||||
|
hover?: boolean;
|
||||||
|
/** 控制滚动方向,默认 up */
|
||||||
|
direction?: "up" | "down" | "left" | "right";
|
||||||
|
/** 单步运动停止的高度,默认 0 */
|
||||||
|
singleHeight?: number;
|
||||||
|
/** 单步运动停止的宽度,默认 0 */
|
||||||
|
singleWidth?: number;
|
||||||
|
/** 单步停止等待时间,默认 1000ms */
|
||||||
|
singleWaitTime?: number;
|
||||||
|
/** 是否开启 rem 度量,默认 false */
|
||||||
|
isRemUnit?: boolean;
|
||||||
|
/** 开启数据更新监听,默认 true */
|
||||||
|
isWatch?: boolean;
|
||||||
|
/** 动画时间,默认 0 */
|
||||||
|
delay?: number;
|
||||||
|
/** 动画方式,默认 ease-in */
|
||||||
|
ease?: string | { x1: number; y1: number; x2: number; y2: number };
|
||||||
|
/** 动画循环次数,-1 表示一直动画,默认 -1 */
|
||||||
|
count?: number;
|
||||||
|
/** 拷贝几份滚动列表,默认 1 */
|
||||||
|
copyNum?: number;
|
||||||
|
/** 开启鼠标悬停时支持滚轮滚动,默认 false */
|
||||||
|
wheel?: boolean;
|
||||||
|
/** 启用单行滚动,默认 false */
|
||||||
|
singleLine?: boolean;
|
||||||
|
/** 自定义类名 */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeamlessScrollRef {
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无缝滚动组件
|
||||||
|
*/
|
||||||
|
declare const SeamlessScroll: ForwardRefExoticComponent<SeamlessScrollProps & RefAttributes<SeamlessScrollRef>>;
|
||||||
|
|
||||||
|
export default SeamlessScroll;
|
||||||
|
|
@ -0,0 +1,516 @@
|
||||||
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||||
|
import { throttle } from "throttle-debounce";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无缝滚动组件
|
||||||
|
*/
|
||||||
|
const SeamlessScroll = forwardRef((props, ref) => {
|
||||||
|
const {
|
||||||
|
value = true,
|
||||||
|
list = [],
|
||||||
|
step = 1,
|
||||||
|
limitScrollNum = 3,
|
||||||
|
hover = true,
|
||||||
|
direction = "up",
|
||||||
|
singleHeight = 0,
|
||||||
|
singleWidth = 0,
|
||||||
|
singleWaitTime = 1000,
|
||||||
|
isRemUnit = false,
|
||||||
|
isWatch = true,
|
||||||
|
delay = 0,
|
||||||
|
ease = "ease-in",
|
||||||
|
count = -1,
|
||||||
|
copyNum = 1,
|
||||||
|
wheel = false,
|
||||||
|
singleLine = false,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// ==================== DOM Refs ====================
|
||||||
|
const scrollRef = useRef(null); // 最外层容器 ref
|
||||||
|
const slotListRef = useRef(null); // 原始列表 ref(用于计算宽度)
|
||||||
|
const realBoxRef = useRef(null); // 实际滚动的容器 ref(包含原始+拷贝内容)
|
||||||
|
|
||||||
|
// ==================== Animation Refs ====================
|
||||||
|
const reqFrame = useRef(null); // requestAnimationFrame 的 ID,用于取消动画
|
||||||
|
const singleWaitTimeout = useRef(null); // 单步滚动等待定时器的 ID
|
||||||
|
const isHoverRef = useRef(false); // 使用 ref 同步跟踪 hover 状态,避免 React state 异步更新问题
|
||||||
|
|
||||||
|
// ==================== States ====================
|
||||||
|
const [realBoxWidth, setRealBoxWidth] = useState(0); // 滚动容器的实际宽度
|
||||||
|
const [realBoxHeight, setRealBoxHeight] = useState(0); // 滚动容器的实际高度(包含原始+拷贝)
|
||||||
|
const [xPos, setXPos] = useState(0); // 当前 X 轴偏移量
|
||||||
|
const [yPos, setYPos] = useState(0); // 当前 Y 轴偏移量
|
||||||
|
const [_count, setCount] = useState(0); // 当前已滚动的循环次数
|
||||||
|
|
||||||
|
// ==================== Computed Values ====================
|
||||||
|
const isScroll = list.length >= limitScrollNum; // 是否需要滚动(列表长度超过限制)
|
||||||
|
|
||||||
|
const realBoxStyle = {
|
||||||
|
width: realBoxWidth ? `${realBoxWidth}px` : "auto",
|
||||||
|
transform: `translate(${xPos}px,${yPos}px)`,
|
||||||
|
transition: `all ${typeof ease === "string" ? ease : `cubic-bezier(${ease.x1}, ${ease.y1}, ${ease.x2}, ${ease.y2})`} ${delay}ms`,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: singleLine ? "flex" : "block",
|
||||||
|
};
|
||||||
|
|
||||||
|
const isHorizontal = direction === "left" || direction === "right";
|
||||||
|
|
||||||
|
const floatStyle = isHorizontal
|
||||||
|
? {
|
||||||
|
float: "left",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: singleLine ? "flex" : "block",
|
||||||
|
flexShrink: singleLine ? 0 : 1,
|
||||||
|
}
|
||||||
|
: { overflow: "hidden" };
|
||||||
|
|
||||||
|
const baseFontSize = isRemUnit
|
||||||
|
? Number.parseInt(window.getComputedStyle(document.documentElement, null).fontSize)
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const realSingleStopWidth = singleWidth * baseFontSize;
|
||||||
|
const realSingleStopHeight = singleHeight * baseFontSize;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (realSingleStopHeight > 0 && realSingleStopHeight % step > 0) {
|
||||||
|
console.error(
|
||||||
|
"如果设置了单步滚动,step 需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [realSingleStopHeight, step]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消当前动画帧
|
||||||
|
*/
|
||||||
|
const cancle = () => {
|
||||||
|
if (reqFrame.current) {
|
||||||
|
cancelAnimationFrame(reqFrame.current);
|
||||||
|
reqFrame.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心动画函数
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 负责执行一帧的滚动动画,包括:
|
||||||
|
* 1. 检查是否到达边界(滚动到一半的高度)
|
||||||
|
* 2. 如果到达边界,重置位置到开头/结尾
|
||||||
|
* 3. 继续滚动一步
|
||||||
|
* 4. 处理单步滚动的等待逻辑
|
||||||
|
*
|
||||||
|
* @param {string} _direction - 滚动方向:'up' | 'down' | 'left' | 'right'
|
||||||
|
* @param {number} _step - 每次滚动的步进距离
|
||||||
|
* @param {boolean} isWheel - 是否由滚轮触发
|
||||||
|
*/
|
||||||
|
const animation = (
|
||||||
|
_direction,
|
||||||
|
_step,
|
||||||
|
isWheel,
|
||||||
|
) => {
|
||||||
|
reqFrame.current = requestAnimationFrame(() => {
|
||||||
|
// 计算一半的高度/宽度(即原始内容的高度/宽度)
|
||||||
|
const h = realBoxHeight / 2;
|
||||||
|
const w = realBoxWidth / 2;
|
||||||
|
|
||||||
|
// 根据滚动方向确保尺寸有效
|
||||||
|
if ((_direction === "up" || _direction === "down") && h <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((_direction === "left" || _direction === "right") && w <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录新位置,用于后续判断是否需要单步等待
|
||||||
|
let newYPos = yPos;
|
||||||
|
let newXPos = xPos;
|
||||||
|
|
||||||
|
// ==================== 向上滚动 ====================
|
||||||
|
if (_direction === "up") {
|
||||||
|
setYPos((prev) => {
|
||||||
|
// 当向上滚动到达边界(已经滚动了一半的高度)时
|
||||||
|
if (Math.abs(prev) >= h) {
|
||||||
|
setCount(c => c + 1);
|
||||||
|
newYPos = -_step;
|
||||||
|
return -_step; // 重置到第一步的位置(而不是0,因为还要继续滚动一步)
|
||||||
|
}
|
||||||
|
newYPos = prev - _step;
|
||||||
|
return prev - _step; // 继续向上滚动一步
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// ==================== 向下滚动 ====================
|
||||||
|
else if (_direction === "down") {
|
||||||
|
setYPos((prev) => {
|
||||||
|
if (prev >= 0) {
|
||||||
|
setCount(c => c + 1);
|
||||||
|
newYPos = (h * -1) + _step;
|
||||||
|
return (h * -1) + _step; // 重置到倒数第一步的位置
|
||||||
|
}
|
||||||
|
newYPos = prev + _step;
|
||||||
|
return prev + _step; // 继续向下滚动一步
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// ==================== 向左滚动 ====================
|
||||||
|
else if (_direction === "left") {
|
||||||
|
setXPos((prev) => {
|
||||||
|
if (Math.abs(prev) >= w) {
|
||||||
|
setCount(c => c + 1);
|
||||||
|
newXPos = -_step;
|
||||||
|
return -_step;
|
||||||
|
}
|
||||||
|
newXPos = prev - _step;
|
||||||
|
return prev - _step; // 继续向左滚动一步
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// ==================== 向右滚动 ====================
|
||||||
|
else if (_direction === "right") {
|
||||||
|
setXPos((prev) => {
|
||||||
|
if (prev >= 0) {
|
||||||
|
setCount(c => c + 1);
|
||||||
|
newXPos = (w * -1) + _step;
|
||||||
|
return (w * -1) + _step;
|
||||||
|
}
|
||||||
|
newXPos = prev + _step;
|
||||||
|
return prev + _step; // 继续向右滚动一步
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是滚轮触发,不继续下一帧
|
||||||
|
if (isWheel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 单步滚动等待逻辑 ====================
|
||||||
|
// 清除之前的等待定时器
|
||||||
|
if (singleWaitTimeout.current) {
|
||||||
|
clearTimeout(singleWaitTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置了单步停止高度(垂直方向)
|
||||||
|
if (realSingleStopHeight) {
|
||||||
|
// 如果当前位置到达了单步停止点,等待一段时间后再继续
|
||||||
|
if (Math.abs(newYPos) % realSingleStopHeight < _step) {
|
||||||
|
singleWaitTimeout.current = window.setTimeout(() => {
|
||||||
|
move();
|
||||||
|
}, singleWaitTime);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
move(); // 立即继续下一帧
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果设置了单步停止宽度(水平方向)
|
||||||
|
else if (realSingleStopWidth) {
|
||||||
|
if (Math.abs(newXPos) % realSingleStopWidth < _step) {
|
||||||
|
singleWaitTimeout.current = window.setTimeout(() => {
|
||||||
|
move();
|
||||||
|
}, singleWaitTime);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没有设置单步停止,持续滚动
|
||||||
|
else {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动下一帧动画
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 检查是否满足滚动条件:
|
||||||
|
* 1. 没有鼠标悬停
|
||||||
|
* 2. 列表长度超过限制
|
||||||
|
* 3. 没有达到最大循环次数
|
||||||
|
* 如果满足条件,调用 animation 执行下一帧
|
||||||
|
*/
|
||||||
|
const move = () => {
|
||||||
|
cancle();
|
||||||
|
if (isHoverRef.current || !isScroll || _count === count) {
|
||||||
|
setCount(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
animation(direction, step, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化滚动
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 计算并设置容器的宽度和高度
|
||||||
|
* 2. 如果需要滚动且 value 为 true,启动滚动动画
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* - React 在 useEffect 中调用,可能存在 DOM 未完全渲染的情况
|
||||||
|
* - 因此使用 requestAnimationFrame 确保 DOM 已渲染
|
||||||
|
*/
|
||||||
|
const initMove = () => {
|
||||||
|
if (list && typeof list !== "boolean" && list.length > 100) {
|
||||||
|
console.warn(`数据达到了${list.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHorizontal) {
|
||||||
|
let slotListWidth = slotListRef.current?.offsetWidth || 0;
|
||||||
|
slotListWidth = slotListWidth * 2 + 1;
|
||||||
|
setRealBoxWidth(slotListWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScroll) {
|
||||||
|
// 使用 requestAnimationFrame 确保 DOM 完全渲染后再获取高度
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const height = realBoxRef.current?.offsetHeight || 0;
|
||||||
|
if (height > 0) {
|
||||||
|
setRealBoxHeight(height);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 如果获取失败,延迟重试
|
||||||
|
setTimeout(() => {
|
||||||
|
const retryHeight = realBoxRef.current?.offsetHeight || 0;
|
||||||
|
if (retryHeight > 0) {
|
||||||
|
setRealBoxHeight(retryHeight);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cancle();
|
||||||
|
setXPos(0);
|
||||||
|
setYPos(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始滚动(鼠标移出时调用)
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 更新 hover 状态为 false
|
||||||
|
* 2. 启动滚动动画
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* 使用 isHoverRef.current 立即更新状态,避免 React state 异步更新导致的问题
|
||||||
|
*/
|
||||||
|
const startMove = () => {
|
||||||
|
isHoverRef.current = false; // 立即更新 ref
|
||||||
|
move();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止滚动(鼠标移入时调用)
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 更新 hover 状态为 true
|
||||||
|
* 2. 清除单步等待定时器
|
||||||
|
* 3. 取消当前动画帧
|
||||||
|
*/
|
||||||
|
const stopMove = () => {
|
||||||
|
isHoverRef.current = true; // 立即更新 ref
|
||||||
|
if (singleWaitTimeout.current) {
|
||||||
|
clearTimeout(singleWaitTimeout.current);
|
||||||
|
}
|
||||||
|
cancle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hoverStop = hover && value && isScroll; // 是否启用悬停停止功能
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置滚动状态
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 取消当前动画
|
||||||
|
* 2. 清除 hover 状态
|
||||||
|
* 3. 重新初始化滚动
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* 对外暴露的方法,可通过 ref 调用
|
||||||
|
*/
|
||||||
|
const reset = () => {
|
||||||
|
cancle();
|
||||||
|
initMove();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚轮事件处理(节流)
|
||||||
|
*/
|
||||||
|
const onWheel = throttle(30, (e) => {
|
||||||
|
cancle();
|
||||||
|
const singleHeight = realSingleStopHeight || 15;
|
||||||
|
if (e.deltaY < 0) {
|
||||||
|
animation("down", singleHeight, true);
|
||||||
|
}
|
||||||
|
if (e.deltaY > 0) {
|
||||||
|
animation("up", singleHeight, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 对外暴露方法
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
reset,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== Effects ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化效果
|
||||||
|
* 当 isScroll 状态改变时,重新初始化滚动
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (isScroll) {
|
||||||
|
initMove();
|
||||||
|
}
|
||||||
|
}, [isScroll]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高度/宽度变化后启动滚动
|
||||||
|
* 当 realBoxHeight 或 realBoxWidth 设置完成后,自动启动滚动
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* 这个 useEffect 解决了 DOM 渲染延迟导致的问题:
|
||||||
|
* - initMove 中异步获取高度
|
||||||
|
* - 高度设置后触发此 useEffect
|
||||||
|
* - 然后启动滚动
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (isScroll && value && (realBoxHeight > 0 || realBoxWidth > 0) && !isHoverRef.current) {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
}, [realBoxHeight, realBoxWidth]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 value 属性变化
|
||||||
|
* 当 value 变化时,启动或停止滚动
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
startMove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stopMove();
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 count 属性变化
|
||||||
|
* 当 count 不为 0 时,重新启动滚动
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* count 表示最大循环次数,达到后会停止
|
||||||
|
* 通过改变 count 属性可以重新启动滚动
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (count !== 0) {
|
||||||
|
startMove();
|
||||||
|
}
|
||||||
|
}, [count]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 list 数据变化
|
||||||
|
* 当 list 或 isWatch 变化时,重置滚动
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (isWatch) {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}, [list, isWatch]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理效果
|
||||||
|
* 组件卸载时,清除所有定时器和动画帧
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancle();
|
||||||
|
if (singleWaitTimeout.current) {
|
||||||
|
clearTimeout(singleWaitTimeout.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成滚动内容的 HTML
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 渲染原始列表
|
||||||
|
* 2. 如果需要滚动,额外渲染一份拷贝
|
||||||
|
* 3. 这样当滚动到一半时,可以无缝重置到开头
|
||||||
|
*/
|
||||||
|
const getHtml = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={slotListRef} style={floatStyle}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{isScroll
|
||||||
|
? Array.from({ length: copyNum }).map((_, index) => (
|
||||||
|
<div key={`copy-${index}`} style={floatStyle}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染组件
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* 1. 根据是否启用滚轮功能,渲染不同的结构
|
||||||
|
* 2. 处理鼠标悬停事件
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
<div ref={scrollRef} className={className}>
|
||||||
|
{/* 如果启用了滚轮和悬停功能,添加 onWheel 事件 */}
|
||||||
|
{wheel && hover
|
||||||
|
? (
|
||||||
|
<div
|
||||||
|
ref={realBoxRef}
|
||||||
|
style={realBoxStyle}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (hoverStop) {
|
||||||
|
stopMove();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
if (hoverStop) {
|
||||||
|
startMove();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onWheel={(e) => {
|
||||||
|
if (hoverStop) {
|
||||||
|
onWheel(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getHtml()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div
|
||||||
|
ref={realBoxRef}
|
||||||
|
style={realBoxStyle}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (hoverStop) {
|
||||||
|
stopMove();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
if (hoverStop) {
|
||||||
|
startMove();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getHtml()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
SeamlessScroll.displayName = "SeamlessScroll";
|
||||||
|
|
||||||
|
export default SeamlessScroll;
|
||||||
Loading…
Reference in New Issue