如何在React.js的容器div中实现可拖拽移动且可左右缩放的div
React实现可拖拽+左右缩放的Div组件
核心思路
通过监听鼠标事件区分拖拽移动和左右缩放操作,结合React状态管理控制元素的位置与尺寸,同时严格限制操作边界,确保元素不会超出父容器范围。
完整实现代码
import { useState, useRef, useEffect } from 'react'; const ResizableDraggableBox = () => { // 容器宽度(用于边界计算) const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(0); // 浅蓝色盒子的状态 const [boxState, setBoxState] = useState({ left: 50, width: 200, minWidth: 50 // 最小宽度限制 }); // 操作状态 const [isDragging, setIsDragging] = useState(false); const [isResizingLeft, setIsResizingLeft] = useState(false); const [isResizingRight, setIsResizingRight] = useState(false); const [startData, setStartData] = useState({ startX: 0, startLeft: 0, startWidth: 0 }); // 获取容器宽度 useEffect(() => { const updateContainerWidth = () => { if (containerRef.current) { setContainerWidth(containerRef.current.offsetWidth); } }; updateContainerWidth(); window.addEventListener('resize', updateContainerWidth); return () => window.removeEventListener('resize', updateContainerWidth); }, []); // 鼠标按下事件:区分拖拽/缩放操作 const handleMouseDown = (e) => { const box = e.currentTarget; const edgeThreshold = 10; // 边缘触发缩放的阈值(px) const mouseXInBox = e.clientX - box.getBoundingClientRect().left; // 判断是否触发左边缘缩放 if (mouseXInBox <= edgeThreshold) { setIsResizingLeft(true); setStartData({ startX: e.clientX, startLeft: boxState.left, startWidth: boxState.width }); } // 判断是否触发右边缘缩放 else if (mouseXInBox >= boxState.width - edgeThreshold) { setIsResizingRight(true); setStartData({ startX: e.clientX, startLeft: boxState.left, startWidth: boxState.width }); } // 触发拖拽 else { setIsDragging(true); setStartData({ startX: e.clientX, startLeft: boxState.left, startWidth: boxState.width }); } }; // 鼠标移动事件:处理拖拽/缩放逻辑 const handleMouseMove = (e) => { if (!isDragging && !isResizingLeft && !isResizingRight) return; const deltaX = e.clientX - startData.startX; let newLeft = startData.startLeft; let newWidth = startData.startWidth; // 处理拖拽移动 if (isDragging) { newLeft = startData.startLeft + deltaX; // 限制边界:不能超出容器左右 newLeft = Math.max(0, Math.min(newLeft, containerWidth - startData.startWidth)); } // 处理左边缘缩放 if (isResizingLeft) { newWidth = startData.startWidth - deltaX; newLeft = startData.startLeft + deltaX; // 限制最小宽度 + 左边界不能小于0 + 右边界不能超出容器 newWidth = Math.max(boxState.minWidth, newWidth); newLeft = Math.max(0, newLeft); newWidth = Math.min(newWidth, containerWidth - newLeft); } // 处理右边缘缩放 if (isResizingRight) { newWidth = startData.startWidth + deltaX; // 限制最小宽度 + 右边界不能超出容器 newWidth = Math.max(boxState.minWidth, newWidth); newWidth = Math.min(newWidth, containerWidth - startData.startLeft); } setBoxState(prev => ({ ...prev, left: newLeft, width: newWidth })); }; // 鼠标抬起/离开窗口:停止所有操作 const handleMouseUp = () => { setIsDragging(false); setIsResizingLeft(false); setIsResizingRight(false); }; useEffect(() => { if (isDragging || isResizingLeft || isResizingRight) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; } }, [isDragging, isResizingLeft, isResizingRight]); return ( <div ref={containerRef} style={{ width: '100%', maxWidth: 800, height: 300, margin: '20px auto', backgroundColor: '#eee', position: 'relative', overflow: 'hidden' }} > <div style={{ position: 'absolute', left: `${boxState.left}px`, top: '20px', width: `${boxState.width}px`, height: '260px', backgroundColor: '#a3d8ff', cursor: isDragging ? 'grabbing' : 'grab' }} onMouseDown={handleMouseDown} onMouseEnter={(e) => { const mouseXInBox = e.clientX - e.target.getBoundingClientRect().left; const edgeThreshold = 10; e.target.style.cursor = mouseXInBox <= edgeThreshold || mouseXInBox >= boxState.width - edgeThreshold ? 'ew-resize' : 'grab'; }} onMouseMove={(e) => { const mouseXInBox = e.clientX - e.target.getBoundingClientRect().left; const edgeThreshold = 10; if (!isDragging && !isResizingLeft && !isResizingRight) { e.target.style.cursor = mouseXInBox <= edgeThreshold || mouseXInBox >= boxState.width - edgeThreshold ? 'ew-resize' : 'grab'; } }} /> </div> ); }; export default ResizableDraggableBox;
关键逻辑说明
- 操作区分:通过鼠标在元素内的位置(边缘10px阈值)判断是拖拽还是缩放,避免操作冲突。
- 边界限制:
- 拖拽时:
left范围限制在0到容器宽度 - 元素当前宽度之间 - 左缩放时:确保元素左边界不小于0,宽度不小于最小值,且右边界不超出容器
- 右缩放时:确保宽度不小于最小值,右边界不超出容器
- 拖拽时:
- 事件绑定:将
mousemove和mouseup绑定到window上,避免鼠标移出元素后操作中断。 - 光标切换:通过
onMouseEnter和onMouseMove动态切换光标,满足边缘缩放、内部拖拽的光标要求。
内容的提问来源于stack exchange,提问作者Mitar Ilic
相关产品推荐
相关产品推荐

