如何让子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; }
关键修改说明
- 容器边界限制
- 将所有widget移动到
#purple-div内部,并设置容器为position: relative,使widget的绝对定位相对于容器 - 在拖拽时计算widget的可移动范围:
left范围为0到容器宽度 - widget当前宽度,top范围为0到容器高度 - widget当前高度,通过Math.max和Math.min限制坐标
- 将所有widget移动到
- 拖拽行为规范
- 保留鼠标与widget的相对偏移量
shiftX/shiftY,当widget触碰到边界后,继续拖动鼠标不会让widget超出边界;只有当鼠标往回移动到与widget的初始相对位置时,widget才会跟随鼠标继续移动
- 保留鼠标与widget的相对偏移量
- 框选逻辑优化
- 将选择框添加到容器内部,避免框选超出容器范围
内容的提问来源于stack exchange,提问作者AKSIL
相关产品推荐
相关产品推荐

