React自定义Resize Hook缩放异常求助:拖拽手柄无法跟随光标
元素缩放React Hook的精度问题分析
我开发了一个实现元素缩放的React Hook,参考相关回答完成代码编写。目前缩放功能可运行,但存在精度问题:缩放手柄无法跟随光标,移动轨迹杂乱无章。
效果预览:
Hook代码
interface Dimensions { width: number | null; height: number | null; } interface UseResizableProps { elementRef: React.RefObject<HTMLDivElement>; handleRef: React.RefObject<HTMLDivElement>; } function useResizable({ elementRef, handleRef }: UseResizableProps) { const dimensions = useRef<Dimensions>({ width: null, height: null }); // 初始化获取元素尺寸 useEffect(() => { if (elementRef.current) { const rect = elementRef.current.getBoundingClientRect(); dimensions.current = { height: rect.height, width: rect.width }; } }, [elementRef]); useEffect(() => { function handleMouseDownE(mouseDownEvent: MouseEvent) { const startPosition = { x: mouseDownEvent.pageX, y: mouseDownEvent.pageY, }; function onMouseMove(mouseMoveEvent: MouseEvent) { if ( dimensions.current.height === null || dimensions.current.width === null ) { return; } dimensions.current = { width: dimensions.current.width - startPosition.x + mouseMoveEvent.pageX, height: dimensions.current.height, }; if ( elementRef?.current?.style.width !== undefined && dimensions.current.width ) { const px = `${dimensions.current.width}px`; elementRef.current.style.width = px; } } function onMouseUp() { document.body.removeEventListener("mousemove", onMouseMove); } document.body?.addEventListener("mousemove", onMouseMove); document.body?.addEventListener("mouseup", onMouseUp, { once: true }); } handleRef?.current?.addEventListener("mousedown", handleMouseDownE); }); }
Hook使用示例
function App() { const elementRef = useRef<HTMLDivElement>(null); const handleRef = useRef<HTMLDivElement>(null); useResizable({ elementRef, handleRef }); return ( <div className="App"> <div className="resizable" ref={elementRef}> <div className="resize-handle" ref={handleRef}></div> </div> </div> ); }
样式代码
.resizable { width: 500px; height: 500px; background-color: lightpink; position: relative; } .resize-handle { width: 5px; height: 100%; background-color: orangered; right: 0; position: absolute; cursor: e-resize; }
问题分析
宽度计算逻辑错误:
每次鼠标移动时,代码用当前已更新的dimensions.current.width减去初始鼠标X再加上当前鼠标X,这会导致偏移量被重复累积。例如第一次移动后宽度变为初始宽度 + (当前X - 初始X),第二次移动时会基于这个新宽度再次计算,最终得到初始宽度 + 2*(当前X - 初始X),完全偏离了光标实际移动的距离。useEffect依赖缺失:
第二个useEffect没有设置依赖数组,会导致组件每次渲染都重新绑定mousedown事件,可能出现重复绑定的情况,引发异常行为。事件清理不完整:
没有在useEffect的返回函数中移除mousedown事件绑定,当组件卸载时会造成内存泄漏。
修复后的Hook代码
interface Dimensions { width: number | null; height: number | null; } interface UseResizableProps { elementRef: React.RefObject<HTMLDivElement>; handleRef: React.RefObject<HTMLDivElement>; } function useResizable({ elementRef, handleRef }: UseResizableProps) { const dimensions = useRef<Dimensions>({ width: null, height: null }); useEffect(() => { if (elementRef.current) { const rect = elementRef.current.getBoundingClientRect(); dimensions.current = { height: rect.height, width: rect.width }; } }, [elementRef]); useEffect(() => { const handleMouseDown = (mouseDownEvent: MouseEvent) => { // 保存鼠标按下时的初始状态:初始宽度和初始鼠标位置 const initialWidth = dimensions.current.width; const startX = mouseDownEvent.pageX; const handleMouseMove = (mouseMoveEvent: MouseEvent) => { if (initialWidth === null) return; // 计算鼠标移动的差值,基于初始宽度更新 const deltaX = mouseMoveEvent.pageX - startX; const newWidth = initialWidth + deltaX; // 可选:添加最小宽度限制,避免缩放到0或负数 if (newWidth >= 100 && elementRef.current) { elementRef.current.style.width = `${newWidth}px`; // 更新ref中的尺寸 dimensions.current.width = newWidth; } }; const handleMouseUp = () => { document.body.removeEventListener("mousemove", handleMouseMove); }; document.body.addEventListener("mousemove", handleMouseMove); document.body.addEventListener("mouseup", handleMouseUp, { once: true }); }; const currentHandle = handleRef.current; currentHandle?.addEventListener("mousedown", handleMouseDown); // 清理事件绑定 return () => { currentHandle?.removeEventListener("mousedown", handleMouseDown); }; }, [elementRef, handleRef]); // 添加依赖数组 }
内容的提问来源于stack exchange,提问作者devklick
相关产品推荐
相关产品推荐

