使用IntersectionObserver检测拖拽元素与未添加.panel--expanded类的.panel元素重叠失败的问题排查
正确姿势:拖拽时实时做碰撞检测
要实现“拖拽元素碰到未展开panel就提示”,咱们得在拖拽过程中实时计算两个元素的边界框,然后判断它们是否相交。具体怎么做呢?
1. 核心方法:用getBoundingClientRect()拿元素边界
每个元素调用getBoundingClientRect()都会返回一个包含top、left、right、bottom的对象,通过这些值就能判断两个矩形是否重叠:
function isOverlapping(rectA, rectB) { // 只有x轴和y轴都有交集,才算重叠 return rectA.right > rectB.left && rectA.left < rectB.right && rectA.bottom > rectB.top && rectA.top < rectB.bottom; }
2. 绑定拖拽事件,实时检测
因为你用了原生draggable属性,咱们可以监听drag事件(拖拽过程中会持续触发),在回调里更新位置+检测碰撞:
完整可运行代码
<div class="panel">panel without panel--expanded class</div> <div class="draggable" draggable="true">draggable element</div> <div class="panel panel--expanded">panel with panel--expanded class</div>
.panel { background-color: lightblue; height: 100px; margin: 50px 0px; width: 500px; } .panel--expanded { background-color: coral; } .draggable { background-color: lightgreen; box-shadow: 5px 5px 5px 5px lightgray; height: 50px; width: 300px; position: absolute; /* 让元素能自由拖拽移动 */ cursor: grab; } .draggable:active { cursor: grabbing; }
const draggable = document.querySelector('.draggable'); const targetPanels = document.querySelectorAll('.panel:not(.panel--expanded)'); let isAlerted = false; // 避免重复弹窗的标记 // 拖拽开始时记录初始偏移 draggable.addEventListener('dragstart', (e) => { const rect = draggable.getBoundingClientRect(); // 记录鼠标相对于元素左上角的位置 e.dataTransfer.setData('text/plain', ''); // 原生拖拽必须设置数据,否则可能失效 draggable.dataset.initialX = e.clientX - rect.left; draggable.dataset.initialY = e.clientY - rect.top; }); // 拖拽过程中实时更新位置+检测碰撞 draggable.addEventListener('drag', (e) => { // 计算拖拽后的位置 const x = e.clientX - Number(draggable.dataset.initialX); const y = e.clientY - Number(draggable.dataset.initialY); draggable.style.left = `${x}px`; draggable.style.top = `${y}px`; // 获取拖拽元素的边界框 const dragRect = draggable.getBoundingClientRect(); // 检查每个目标panel是否重叠 targetPanels.forEach(panel => { const panelRect = panel.getBoundingClientRect(); if (isOverlapping(dragRect, panelRect) && !isAlerted) { alert('OVERLAPPING!'); isAlerted = true; // 1秒后重置标记,允许再次触发 setTimeout(() => isAlerted = false, 1000); } }); }); // 碰撞检测工具函数 function isOverlapping(rectA, rectB) { return rectA.right > rectB.left && rectA.left < rectB.right && rectA.bottom > rectB.top && rectA.top < rectB.bottom; }
几个小细节说明
- 给
.draggable加了position: absolute:原生draggable默认不会改变元素位置,必须手动设置定位才能让它跟着鼠标动。 - 加了
isAlerted标记:避免拖拽过程中连续弹出alert,影响体验。 - 如果不想用原生拖拽,换成
mousedown/mousemove/mouseup实现自定义拖拽,逻辑完全一样,只是事件不同。
内容的提问来源于stack exchange,提问作者webta.st.ic




