原生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
相关产品推荐
相关产品推荐

