ReactJS实现鼠标按下或按钮悬停时的平滑连续滚动效果求助
问题根因
- 你当前实现卡顿的核心原因是
setInterval与scrollBy({behavior: 'smooth'})的动画逻辑冲突:smooth参数本身会触发浏览器自带的滚动过渡动画,叠加固定间隔的滚动调用会导致动画队列阻塞,出现跳帧、不连贯的问题 - 其次
setInterval的执行时机无法和浏览器的渲染帧对齐,无法保证60fps的流畅度
优化实现思路
- 用
requestAnimationFrame替代setInterval实现滚动逻辑,和浏览器渲染周期对齐,保证动画流畅 - 移除
scrollBy的smooth参数,每帧直接计算滚动增量,避免多重动画冲突 - 增加滚动边界判断,滚动到容器顶部/底部时自动停止,避免无效计算
- 如果后续要实现Etsy的悬停滚动效果,只需要额外监听容器的
mouseover事件,根据鼠标在容器的Y轴坐标动态调整滚动方向和速度即可
可运行的平滑滚动示例代码
import { useRef, useState, useLayoutEffect, useEffect } from "react"; import styled from "styled-components"; const Wrapper = styled.div` margin: 5% 40%; .test { height: 300px; width: 100px; overflow: hidden; border: 1px solid #eee; .inner { background-image: linear-gradient(red, blue); height: 800px; width: 100%; } } button { margin-top: 10px; padding: 8px 12px; } `; // 替代setInterval的帧动画hook,和浏览器渲染对齐 function useAnimationFrame(callback, isRunning) { const savedCallback = useRef(callback); const frameIdRef = useRef(); useLayoutEffect(() => { savedCallback.current = callback; }, [callback]); useEffect(() => { if (!isRunning) return; const loop = () => { savedCallback.current(); frameIdRef.current = requestAnimationFrame(loop); }; frameIdRef.current = requestAnimationFrame(loop); return () => cancelAnimationFrame(frameIdRef.current); }, [isRunning]); } const Scroll = () => { const scrollRef = useRef(null); const [isScrolling, setIsScrolling] = useState(false); // 可调整滚动速度,数值越大滚动越快 const scrollStep = 2; useAnimationFrame(() => { if (!scrollRef.current) return; const el = scrollRef.current; // 滚动到边界自动停止 if (el.scrollTop + el.clientHeight >= el.scrollHeight) return; el.scrollTop += scrollStep; }, isScrolling); return ( <Wrapper> <div ref={scrollRef} className="test"> <div className="inner"></div> </div> <button onMouseDown={() => setIsScrolling(true)} onMouseUp={() => setIsScrolling(false)} onMouseLeave={() => setIsScrolling(false)} > HOLD DOWN TO SCROLL </button> </Wrapper> ); }; export default Scroll;
扩展Etsy悬停滚动的补充逻辑
如果要实现悬停区域触发滚动的效果,只需要给滚动容器添加以下事件逻辑:
- 监听
mousemove事件,计算鼠标当前在容器内的Y轴位置 - 当鼠标在容器顶部20%区域时,设置滚动步长为负数(向上滚动)
- 当鼠标在容器底部20%区域时,设置滚动步长为正数(向下滚动)
- 当鼠标在中间区域时,停止滚动
- 监听
mouseleave事件,离开容器时自动停止滚动
内容的提问来源于stack exchange,提问作者Lberteh
相关产品推荐
相关产品推荐

