You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;

关键逻辑说明

  1. 操作区分:通过鼠标在元素内的位置(边缘10px阈值)判断是拖拽还是缩放,避免操作冲突。
  2. 边界限制:
    • 拖拽时:left 范围限制在 0 到 容器宽度 - 元素当前宽度 之间
    • 左缩放时:确保元素左边界不小于0,宽度不小于最小值,且右边界不超出容器
    • 右缩放时:确保宽度不小于最小值,右边界不超出容器
  3. 事件绑定:将mousemove和mouseup绑定到window上,避免鼠标移出元素后操作中断。
  4. 光标切换:通过onMouseEnter和onMouseMove动态切换光标,满足边缘缩放、内部拖拽的光标要求。

内容的提问来源于stack exchange,提问作者Mitar Ilic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:25:10