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

原生JS实现父容器内不重叠随机div及拖动防碰撞功能求助

解决随机不重叠Div创建及带碰撞检测的拖动问题

一、修复Div创建时的重叠问题

要让新生成的Div不与已有Div重叠,需实现矩形碰撞检测函数,并在生成坐标时循环验证,直到找到合法位置(不重叠且在容器内)。

核心逻辑:碰撞检测函数

判断两个矩形是否重叠,只需检查它们的边界是否存在分离情况:

function isOverlapping(newBox, existingBox) {
  const newRect = newBox.getBoundingClientRect();
  const existingRect = existingBox.getBoundingClientRect();
  const containerRect = container.getBoundingClientRect();

  // 先检查新box是否超出容器边界
  const isOutOfBounds = 
    newRect.left < containerRect.left ||
    newRect.right > containerRect.right ||
    newRect.top < containerRect.top ||
    newRect.bottom > containerRect.bottom;

  if (isOutOfBounds) return true;

  // 检查与已有box是否重叠
  return !(
    newRect.right < existingRect.left ||
    newRect.left > existingRect.right ||
    newRect.bottom < existingRect.top ||
    newRect.top > existingRect.bottom
  );
}

修改创建Div的逻辑

在addDiv函数中,生成尺寸后循环生成坐标,直到找到不重叠的位置:

function addDiv() {
  const div = document.createElement("div");
  div.className = 'box';
  
  // 先随机生成尺寸
  const height = getRndInteger(20, 100);
  const width = getRndInteger(20, 100);
  div.style.height = `${height}px`;
  div.style.width = `${width}px`;
  div.style.background = backgroundColor();

  // 获取容器边界
  const containerRect = container.getBoundingClientRect();
  const maxTop = containerRect.height - height;
  const maxLeft = containerRect.width - width;
  let isValidPosition = false;

  // 循环生成坐标直到找到合法位置
  while (!isValidPosition) {
    const top = getRndInteger(0, maxTop);
    const left = getRndInteger(0, maxLeft);
    div.style.top = `${top}px`;
    div.style.left = `${left}px`;

    // 检查是否与所有已有box重叠
    const existingBoxes = document.querySelectorAll('.box');
    isValidPosition = Array.from(existingBoxes).every(box => {
      if (box === div) return true; // 跳过自身
      return !isOverlapping(div, box);
    });
  }

  // 初始化原始位置,用于拖动碰撞恢复
  div.originalLeft = parseInt(div.style.left);
  div.originalTop = parseInt(div.style.top);
  
  container.appendChild(div);
  // 绑定拖动事件
  bindDragEvents(div);
}

二、实现带碰撞检测的拖动功能

拖动时需实时计算新位置,检查是否与其他Div碰撞或超出容器,若合法才更新位置。

拖动事件绑定函数

给每个Div添加mousedown、mousemove、mouseup事件:

function bindDragEvents(box) {
  let isDragging = false;
  let offsetX, offsetY;
  const containerRect = container.getBoundingClientRect();

  box.addEventListener('mousedown', (e) => {
    isDragging = true;
    // 记录鼠标相对于box左上角的偏移量
    offsetX = e.clientX - box.getBoundingClientRect().left;
    offsetY = e.clientY - box.getBoundingClientRect().top;
    // 提升层级避免被遮挡
    box.style.zIndex = 100;
  });

  document.addEventListener('mousemove', (e) => {
    if (!isDragging) return;

    // 计算新位置(基于容器的相对位置)
    const newLeft = e.clientX - containerRect.left - offsetX;
    const newTop = e.clientY - containerRect.top - offsetY;
    const boxWidth = box.getBoundingClientRect().width;
    const boxHeight = box.getBoundingClientRect().height;

    // 边界检查:不能超出容器
    const clampedLeft = Math.max(0, Math.min(newLeft, containerRect.width - boxWidth));
    const clampedTop = Math.max(0, Math.min(newTop, containerRect.height - boxHeight));

    // 临时设置位置用于碰撞检测
    box.style.left = `${clampedLeft}px`;
    box.style.top = `${clampedTop}px`;

    // 检查与其他box是否碰撞
    let hasCollision = false;
    document.querySelectorAll('.box').forEach(otherBox => {
      if (otherBox !== box && isOverlapping(box, otherBox)) {
        hasCollision = true;
      }
    });

    // 如果碰撞,恢复之前的合法位置
    if (hasCollision) {
      box.style.left = `${box.originalLeft}px`;
      box.style.top = `${box.originalTop}px`;
    } else {
      // 保存当前位置作为下次碰撞时的恢复点
      box.originalLeft = clampedLeft;
      box.originalTop = clampedTop;
    }
  });

  document.addEventListener('mouseup', () => {
    if (isDragging) {
      isDragging = false;
      box.style.zIndex = 1; // 恢复层级
    }
  });
}

完整代码

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Random Draggable Boxes</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class='wrapper'></div>
  <button id="create">Create Box</button>
  <button id="clear">Clear All</button>

  <script src="script.js"></script>
</body>
</html>

CSS

.box {
  position: absolute;
  cursor: move;
  border: 1px solid #333;
}

.wrapper {
  height: 900px;
  width: 900px;
  background-color: blue;
  position: relative;
  margin-bottom: 20px;
}

button {
  padding: 8px 16px;
  margin-right: 10px;
}

JavaScript

const container = document.querySelector('.wrapper');

function backgroundColor() {
  // 随机颜色增加区分度
  const colors = ['#d1d11d', '#1d91d1', '#d11d91', '#1dd14d'];
  return colors[getRndInteger(0, colors.length)];
}

function getRndInteger(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

function isOverlapping(newBox, existingBox) {
  const newRect = newBox.getBoundingClientRect();
  const existingRect = existingBox.getBoundingClientRect();
  const containerRect = container.getBoundingClientRect();

  // 检查是否超出容器
  const isOutOfBounds = 
    newRect.left < containerRect.left ||
    newRect.right > containerRect.right ||
    newRect.top < containerRect.top ||
    newRect.bottom > containerRect.bottom;

  if (isOutOfBounds) return true;

  // 检查是否与已有box重叠
  return !(
    newRect.right < existingRect.left ||
    newRect.left > existingRect.right ||
    newRect.bottom < existingRect.top ||
    newRect.top > existingRect.bottom
  );
}

function bindDragEvents(box) {
  let isDragging = false;
  let offsetX, offsetY;
  const containerRect = container.getBoundingClientRect();

  box.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.clientX - box.getBoundingClientRect().left;
    offsetY = e.clientY - box.getBoundingClientRect().top;
    box.style.zIndex = 100;
  });

  document.addEventListener('mousemove', (e) => {
    if (!isDragging) return;

    const newLeft = e.clientX - containerRect.left - offsetX;
    const newTop = e.clientY - containerRect.top - offsetY;
    const boxWidth = box.getBoundingClientRect().width;
    const boxHeight = box.getBoundingClientRect().height;

    // 边界限制
    const clampedLeft = Math.max(0, Math.min(newLeft, containerRect.width - boxWidth));
    const clampedTop = Math.max(0, Math.min(newTop, containerRect.height - boxHeight));

    // 临时设置位置
    box.style.left = `${clampedLeft}px`;
    box.style.top = `${clampedTop}px`;

    // 碰撞检测
    let hasCollision = false;
    document.querySelectorAll('.box').forEach(otherBox => {
      if (otherBox !== box && isOverlapping(box, otherBox)) {
        hasCollision = true;
      }
    });

    if (hasCollision) {
      // 恢复到上一合法位置
      box.style.left = `${box.originalLeft}px`;
      box.style.top = `${box.originalTop}px`;
    } else {
      // 更新合法位置记录
      box.originalLeft = clampedLeft;
      box.originalTop = clampedTop;
    }
  });

  document.addEventListener('mouseup', () => {
    if (isDragging) {
      isDragging = false;
      box.style.zIndex = 1;
    }
  });
}

function addDiv() {
  const div = document.createElement("div");
  div.className = 'box';
  
  const height = getRndInteger(20, 100);
  const width = getRndInteger(20, 100);
  div.style.height = `${height}px`;
  div.style.width = `${width}px`;
  div.style.background = backgroundColor();

  const containerRect = container.getBoundingClientRect();
  const maxTop = containerRect.height - height;
  const maxLeft = containerRect.width - width;
  let isValidPosition = false;

  while (!isValidPosition) {
    const top = getRndInteger(0, maxTop);
    const left = getRndInteger(0, maxLeft);
    div.style.top = `${top}px`;
    div.style.left = `${left}px`;

    const existingBoxes = document.querySelectorAll('.box');
    isValidPosition = Array.from(existingBoxes).every(box => {
      if (box === div) return true;
      return !isOverlapping(div, box);
    });
  }

  // 初始化原始位置
  div.originalLeft = parseInt(div.style.left);
  div.originalTop = parseInt(div.style.top);
  
  container.appendChild(div);
  bindDragEvents(div);
}

// 绑定按钮事件
document.querySelector('#create').addEventListener('click', addDiv);
document.getElementById('clear').addEventListener('click', () => {
  container.innerHTML = '';
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 10:21:13