如何判断圆形碰撞?求两圆无重叠移动的高效实现方案
嘿,我来帮你搞定这个拖动圆形不与固定中心圆重叠的性能问题!
高效实现拖动圆形不重叠的方案
首先拆解下你现有实现可能的性能瓶颈:这类交互卡顿大多源于频繁触发DOM重排/重绘(比如每次拖动都同步修改元素位置并重复计算),或是没利用浏览器的动画调度API,导致计算和渲染不同步。
下面是优化后的高效实现思路和完整代码:
核心优化点
- 用
requestAnimationFrame调度位置更新,让计算和浏览器渲染帧同步,避免丢帧 - 预计算固定圆的中心、半径等静态值,避免每次拖动重复计算
- 用
transform更新元素位置(比修改top/left性能高,因为触发合成层操作而非重排)
完整实现代码
<!DOCTYPE html> <html> <head> <style> body { margin: 0; height: 100vh; position: relative; overflow: hidden; } .circle { position: absolute; border-radius: 50%; } .fixed-circle { width: 100px; height: 100px; background: #2ecc71; left: 50%; top: 50%; transform: translate(-50%, -50%); } .draggable-circle { width: 100px; height: 100px; background: #3498db; cursor: move; } </style> </head> <body> <div class="circle fixed-circle"></div> <div class="circle draggable-circle"></div> <script> // 获取元素 const fixedCircle = document.querySelector('.fixed-circle'); const draggableCircle = document.querySelector('.draggable-circle'); // 预计算静态参数 const fixedRadius = fixedCircle.offsetWidth / 2; const fixedRect = fixedCircle.getBoundingClientRect(); const fixedCenter = { x: fixedRect.left + fixedRadius, y: fixedRect.top + fixedRadius }; const draggableRadius = draggableCircle.offsetWidth / 2; const minSafeDistance = fixedRadius + draggableRadius; // 两圆不重叠的最小中心距 let isDragging = false; let mouseOffset = { x: 0, y: 0 }; // 碰撞检测与位置修正逻辑 function updateDraggablePos(clientX, clientY) { // 计算拖动目标的原始中心位置 const targetCenter = { x: clientX - mouseOffset.x, y: clientY - mouseOffset.y }; // 计算两中心的距离 const dx = targetCenter.x - fixedCenter.x; const dy = targetCenter.y - fixedCenter.y; const distance = Math.sqrt(dx * dx + dy * dy); let finalCenter = targetCenter; // 如果距离小于安全值,修正到刚好不重叠的位置 if (distance < minSafeDistance) { const scaleRatio = minSafeDistance / distance; finalCenter = { x: fixedCenter.x + dx * scaleRatio, y: fixedCenter.y + dy * scaleRatio }; } // 转换为元素左上角坐标,用transform更新位置 const left = finalCenter.x - draggableRadius; const top = finalCenter.y - draggableRadius; draggableCircle.style.transform = `translate(${left}px, ${top}px)`; } // 拖动事件绑定 draggableCircle.addEventListener('mousedown', (e) => { isDragging = true; // 计算鼠标相对于拖动圆左上角的偏移量 const rect = draggableCircle.getBoundingClientRect(); mouseOffset.x = e.clientX - rect.left; mouseOffset.y = e.clientY - rect.top; e.preventDefault(); // 防止拖动时选中文字 }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; // 用requestAnimationFrame调度更新,避免频繁执行 requestAnimationFrame(() => updateDraggablePos(e.clientX, e.clientY)); }); document.addEventListener('mouseup', () => { isDragging = false; }); // 窗口 resize 时重新计算固定圆中心(可选) window.addEventListener('resize', () => { const rect = fixedCircle.getBoundingClientRect(); fixedCenter.x = rect.left + fixedRadius; fixedCenter.y = rect.top + fixedRadius; }); </script> </body> </html>
关键性能细节说明
transform更新位置:修改transform只会触发浏览器的合成层操作,比修改top/left(触发重排)的性能高出一大截,高频拖动场景下优势明显。- 静态值预计算:固定圆的中心、半径这些不会变化的值只计算一次,窗口resize时再重新计算,避免每次拖动都重复做无用功。
requestAnimationFrame调度:把计算和位置更新放入这个API,让浏览器在每帧渲染的最佳时机执行,避免同步执行导致的主线程阻塞。- 减少DOM查询:只在鼠标按下和窗口resize时获取元素位置,避免在
mousemove中频繁查询DOM节点信息。
这样实现的效果会和你预期的一样流畅,同时性能拉满,不会出现卡顿情况。
内容的提问来源于stack exchange,提问作者Agustin Val
相关产品推荐
相关产品推荐

