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

如何让子DIV在父DIV内拖拽调整不溢出,规范拖拽鼠标行为

解决Widget拖拽边界限制与拖拽行为规范问题

需求说明

  • 所有.widgets组件必须限制在#purple-div容器内,禁止超出容器边界
  • 拖拽时保持鼠标与widget的相对位置:当widget触碰到容器边界后,只有鼠标回到拖拽起始的相对位置,才能继续拖动widget移动

修改后的完整代码

HTML

<div id="purple-div">
  <div id="widget1" class="widgets" style="left: 50px; top: 50px;">
    <div id="widget1header" class="widget-header"></div>
  </div>

  <div id="widget2" class="widgets" style="left: 150px; top: 150px;">
    <div id="widget2header" class="widget-header"></div>
  </div>

  <div id="widget3" class="widgets" style="left: 250px; top: 250px;">
    <div id="widget3header" class="widget-header"></div>
  </div>
</div>

CSS

#selection-rectangle {
  position: absolute;
  border: 2px dashed blue;
  pointer-events: none;
  display: none;
  z-index: 9999999;
}

.widgets.selected {
  outline-color: blue;
  outline-width: 2px;
  outline-style: dashed;
}

.widgets {
  position: absolute;
  z-index: 9;
  background-color: #000000;
  color: white;
  font-size: 25px;
  font-family: Arial, Helvetica, sans-serif;
  border: 2px solid #212128;
  text-align: center;
  width: 500px;
  height: 200px;
  min-width: 166px;
  min-height: 66px;
  overflow: hidden;
  resize: both;
  image-resolution: none;
  border-radius: 10px;
}

.widget-header {
  padding: 10px;
  cursor: move;
  z-index: 10;
  background-color: #040c14;
  outline-color: white;
  outline-width: 2px;
  outline-style: solid;
}

#purple-div {
  width: 1000px;
  height: 700px;
  background-color: purple;
  position: relative;
  z-index: 1;
}

JavaScript

let isSelecting = false;
let selectionStartX, selectionStartY, selectionEndX, selectionEndY;
let selectionRectangle;
let draggedElements = [];
const widgets = document.querySelectorAll('.widgets');
const purpleContainer = document.getElementById('purple-div');

document.addEventListener('mousedown', (event) => {
  const header = event.target.closest('.widget-header');
  if (header) {
    const widget = header.parentElement;
    // 处理选中状态
    if (!widget.classList.contains('selected')) {
      widgets.forEach(w => w.classList.remove('selected'));
      draggedElements = [widget];
    } else {
      draggedElements = Array.from(widgets).filter(w => w.classList.contains('selected'));
    }

    draggedElements.forEach((widget) => {
      // 获取鼠标相对于widget左上角的偏移量
      const widgetRect = widget.getBoundingClientRect();
      const shiftX = event.clientX - widgetRect.left;
      const shiftY = event.clientY - widgetRect.top;
      // 获取容器的边界信息
      const containerRect = purpleContainer.getBoundingClientRect();

      function moveElement(event) {
        // 计算widget相对于容器的目标坐标
        const relativeX = event.clientX - containerRect.left - shiftX;
        const relativeY = event.clientY - containerRect.top - shiftY;
        
        // 获取widget当前的尺寸(支持resize后的动态尺寸)
        const currentWidgetRect = widget.getBoundingClientRect();
        const widgetWidth = currentWidgetRect.width;
        const widgetHeight = currentWidgetRect.height;

        // 限制坐标在容器范围内,避免超出边界
        const clampedX = Math.max(0, Math.min(relativeX, containerRect.width - widgetWidth));
        const clampedY = Math.max(0, Math.min(relativeY, containerRect.height - widgetHeight));

        // 更新widget位置
        widget.style.left = clampedX + 'px';
        widget.style.top = clampedY + 'px';
      }

      function stopMoving() {
        document.removeEventListener('mousemove', moveElement);
        document.removeEventListener('mouseup', stopMoving);
      }

      document.addEventListener('mousemove', moveElement);
      document.addEventListener('mouseup', stopMoving);
    });

    return;
  }

  // 处理框选逻辑
  const isWidgetClicked = Array.from(widgets).some(widget => {
    const rect = widget.getBoundingClientRect();
    return event.clientX >= rect.left && event.clientX <= rect.right &&
           event.clientY >= rect.top && event.clientY <= rect.bottom;
  });

  if (!isWidgetClicked && event.target !== selectionRectangle) {
    isSelecting = true;
    selectionStartX = event.clientX;
    selectionStartY = event.clientY;
    
    // 创建选择框并添加到容器内
    selectionRectangle = document.createElement('div');
    selectionRectangle.id = 'selection-rectangle';
    selectionRectangle.style.position = 'absolute';
    selectionRectangle.style.border = '2px dashed blue';
    selectionRectangle.style.pointerEvents = 'none';
    selectionRectangle.style.display = 'none';
    purpleContainer.appendChild(selectionRectangle);

    // 清除所有选中状态
    widgets.forEach(w => w.classList.remove('selected'));
  }
});

document.addEventListener('mousemove', (event) => {
  if (isSelecting) {
    selectionEndX = event.clientX;
    selectionEndY = event.clientY;

    const width = Math.abs(selectionEndX - selectionStartX);
    const height = Math.abs(selectionEndY - selectionStartY);
    const left = Math.min(selectionEndX, selectionStartX);
    const top = Math.min(selectionEndY, selectionStartY);

    // 更新选择框位置和尺寸
    selectionRectangle.style.width = width + 'px';
    selectionRectangle.style.height = height + 'px';
    selectionRectangle.style.left = (left - purpleContainer.getBoundingClientRect().left) + 'px';
    selectionRectangle.style.top = (top - purpleContainer.getBoundingClientRect().top) + 'px';
    selectionRectangle.style.display = 'block';

    // 判断widget是否被选中
    widgets.forEach((widget) => {
      const widgetRect = widget.getBoundingClientRect();
      const isIntersecting = isRectangleIntersecting(widgetRect, {
        x: left,
        y: top,
        width,
        height,
      });
      widget.classList.toggle('selected', isIntersecting);
    });
  }
});

document.addEventListener('mouseup', () => {
  if (isSelecting) {
    isSelecting = false;
    selectionRectangle.remove();
  }
});

// 判断矩形是否完全包含(原逻辑保留)
function isRectangleIntersecting(rect1, rect2) {
  return rect1.left >= rect2.x &&
         rect1.top >= rect2.y &&
         rect1.right <= rect2.x + rect2.width &&
         rect1.bottom <= rect2.y + rect2.height;
}

关键修改说明

  1. 容器边界限制
    • 将所有widget移动到#purple-div内部,并设置容器为position: relative,使widget的绝对定位相对于容器
    • 在拖拽时计算widget的可移动范围:left范围为0到容器宽度 - widget当前宽度,top范围为0到容器高度 - widget当前高度,通过Math.max和Math.min限制坐标
  2. 拖拽行为规范
    • 保留鼠标与widget的相对偏移量shiftX/shiftY,当widget触碰到边界后,继续拖动鼠标不会让widget超出边界;只有当鼠标往回移动到与widget的初始相对位置时,widget才会跟随鼠标继续移动
  3. 框选逻辑优化
    • 将选择框添加到容器内部,避免框选超出容器范围

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:17:08