React TypeScript组件实现鼠标拖拽横向滚动问题求助
React横向拖拽滚动组件问题排查与修复
问题概述
我正在实现一个可复用React组件,支持鼠标点击拖拽实现横向滚动,效果类似触控板/触摸屏的滚动。
预期行为
被包裹的Div能跟随鼠标拖拽动作左右滚动
实际行为
点击拖拽无法触发滚动,但鼠标放在目标Div上横向移动时,Div会有轻微滚动效果。
我尝试过调整滚动条、增删事件监听器等方法,还试图把一个非React环境下的可用示例适配到React组件中。这个组件用来包裹应用中任何有横向溢出的div,通过useRef定位子div的逻辑是正常的。
原组件代码
import React, { useState, ReactNode, useRef, RefObject, useEffect } from "react"; type DraggableProps = { innerRef: RefObject<HTMLDivElement>; rootClass: string; children: ReactNode; }; export const Draggable = ({ innerRef, rootClass = "", children }: DraggableProps) => { const slider = innerRef.current as HTMLElement; const ourRef = useRef<HTMLDivElement>(null); const [pageX, setPageX] = useState(0); const [scrollX, setScrollX] = useState(0); const [isScrolling, setIsScrolling] = useState(false); useEffect(() => { handleMouseDown() },[isScrolling]) const handleMouseDown = () => { setIsScrolling(true); slider?.addEventListener("mousedown", (e) => { console.log("Mouse down => event", e); setPageX(e.pageX - slider.offsetLeft); setScrollX(slider?.scrollLeft); }); slider?.addEventListener("mouseleave", () => { console.log("Mouse leave"); slider.removeEventListener("mousedown", () => {console.log("remove mouse down"); }); slider.removeEventListener("mousemove", () => {console.log("remove mouse move"); }); setIsScrolling(false); }); slider?.addEventListener("mouseup", () => { console.log("Mouse up"); slider.removeEventListener("mousedown", () => {console.log("remove mouse down"); }); slider.removeEventListener("mousemove", () => {console.log("remove mouse move"); }); setIsScrolling(false); }); slider?.addEventListener("mousemove", (e) => { if (!isScrolling) return; e.preventDefault(); console.log("Mouse move => pageX", pageX); const x = e.pageX - slider?.offsetLeft; const walk = x - pageX; slider.scrollLeft = scrollX - walk; }); }; return ( <div ref={ourRef} onMouseDown={handleMouseDown} className={rootClass}> {children} </div> ); };
问题根源
- 事件监听器绑定逻辑错误:在
handleMouseDown中重复绑定mousedown事件,且移除监听器时使用匿名函数,无法正确移除(每次创建的匿名函数都是新引用)。 - 状态更新时机问题:
useEffect依赖isScrolling,每次状态变化都会调用handleMouseDown,导致重复绑定事件。 - 拖拽状态控制逻辑混乱:
isScrolling在handleMouseDown一开始就被设为true,但实际应该在用户真正点击(mousedown触发)后才开启拖拽状态。 - React合成事件与原生事件混用冲突:组件根元素绑定了
onMouseDown合成事件,同时又给slider绑定原生mousedown事件,导致逻辑冲突。
修复后的代码
import React, { useState, ReactNode, useRef, RefObject, useEffect } from "react"; type DraggableProps = { innerRef: RefObject<HTMLDivElement>; rootClass: string; children: ReactNode; }; export const Draggable = ({ innerRef, rootClass = "", children }: DraggableProps) => { const [isDragging, setIsDragging] = useState(false); const startX = useRef(0); const startScrollLeft = useRef(0); useEffect(() => { const slider = innerRef.current; if (!slider) return; const handleMouseDown = (e: MouseEvent) => { setIsDragging(true); startX.current = e.pageX - slider.offsetLeft; startScrollLeft.current = slider.scrollLeft; // 防止文本选中干扰拖拽 slider.style.userSelect = "none"; }; const handleMouseMove = (e: MouseEvent) => { if (!isDragging || !slider) return; e.preventDefault(); const x = e.pageX - slider.offsetLeft; const walk = x - startX.current; slider.scrollLeft = startScrollLeft.current - walk; }; const handleMouseUp = () => { setIsDragging(false); if (slider) { slider.style.userSelect = ""; } }; // 绑定事件 slider.addEventListener("mousedown", handleMouseDown); document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); // 清理函数,组件卸载时移除事件 return () => { slider.removeEventListener("mousedown", handleMouseDown); document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); }; }, [innerRef, isDragging]); return ( <div className={rootClass}> {children} </div> ); };
修复说明
- 事件绑定优化:在
useEffect中一次性绑定所有必要事件,使用具名函数确保能正确移除监听器,组件卸载时自动清理事件,避免内存泄漏。 - 拖拽状态控制:只有在
mousedown触发后才设置isDragging为true,确保拖拽逻辑仅在用户点击后生效。 - 临时值存储优化:拖拽起始的
startX和startScrollLeft使用useRef存储,避免因状态更新导致的重新渲染和值丢失问题。 - 全局事件监听:将
mousemove和mouseup绑定到document上,确保即使鼠标移出滑块区域,也能正常结束拖拽。 - 用户体验优化:拖拽时禁用滑块内的文本选择,避免拖拽时选中文本影响操作体验。
内容的提问来源于stack exchange,提问作者Hamilton S
相关产品推荐
相关产品推荐

