如何使用JavaScript判断矩形与旋转矩形是否相交
解决旋转矩形的SVG碰撞检测问题
嘿,我来帮你搞定这个旋转矩形的碰撞检测需求!你找到的那段用getBoundingClientRect()的代码有个关键问题:它返回的是轴对齐的包围盒(AABB)——不管你的矩形怎么旋转,这个方法都会生成一个和屏幕坐标轴平行的矩形框。用它来检测旋转后的矩形碰撞,会出现很多误判(比如两个旋转后的矩形实际没接触,但它们的AABB重叠了),完全满足不了你的需求。
正确的解决方案是用分离轴定理(Separating Axis Theorem, SAT),这是检测凸多边形碰撞的黄金标准,而矩形刚好属于凸多边形,完美适配。下面是具体的实现步骤和代码:
步骤1:获取矩形的实际顶点坐标
首先需要一个工具函数,用来获取SVG矩形在经过CSS/SVG变换后的四个顶点的真实坐标:
function getPolygonVertices(element) { // 获取元素的变换矩阵(包含旋转、平移、缩放等所有变换) const transform = element.getCTM(); // 获取矩形的基础边界(未变换的原始尺寸) const rect = element.getBBox(); const x = rect.x; const y = rect.y; const width = rect.width; const height = rect.height; // 定义矩形的四个原始顶点 const rawVertices = [ { x: x, y: y }, { x: x + width, y: y }, { x: x + width, y: y + height }, { x: x, y: y + height } ]; // 应用变换矩阵,计算出实际显示的顶点坐标 return rawVertices.map(vertex => { return { x: vertex.x * transform.a + vertex.y * transform.c + transform.e, y: vertex.x * transform.b + vertex.y * transform.d + transform.f }; }); }
步骤2:实现分离轴定理核心逻辑
接下来实现SAT的核心函数,用来判断两个凸多边形(这里就是两个矩形的顶点集合)是否碰撞:
// 主函数:判断两个多边形是否碰撞 function doPolygonsIntersect(poly1, poly2) { // 获取两个多边形所有边的法线(这些是候选的分离轴) const axes = [...getAxes(poly1), ...getAxes(poly2)]; // 遍历每个轴,检查是否能分离两个多边形 for (const axis of axes) { const [min1, max1] = projectPolygon(poly1, axis); const [min2, max2] = projectPolygon(poly2, axis); // 如果投影区间不重叠,说明存在分离轴,没有碰撞 if (max1 < min2 || max2 < min1) { return false; } } // 所有轴都无法分离,说明两个多边形碰撞 return true; } // 获取多边形所有边的法线轴 function getAxes(polygon) { const axes = []; const vertexCount = polygon.length; for (let i = 0; i < vertexCount; i++) { const currentVertex = polygon[i]; const nextVertex = polygon[(i + 1) % vertexCount]; // 计算边的向量 const edge = { x: nextVertex.x - currentVertex.x, y: nextVertex.y - currentVertex.y }; // 生成垂直于边的法线(分离轴) const normal = { x: -edge.y, y: edge.x }; // 归一化法线,避免数值精度问题 axes.push(normalizeVector(normal)); } return axes; } // 将多边形投影到指定轴上,返回投影的最小和最大值 function projectPolygon(polygon, axis) { let minProjection = dotProduct(polygon[0], axis); let maxProjection = minProjection; for (const vertex of polygon) { const projection = dotProduct(vertex, axis); if (projection < minProjection) minProjection = projection; if (projection > maxProjection) maxProjection = projection; } return [minProjection, maxProjection]; } // 计算向量点积 function dotProduct(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } // 归一化向量 function normalizeVector(v) { const length = Math.sqrt(v.x * v.x + v.y * v.y); return { x: v.x / length, y: v.y / length }; }
步骤3:组合成最终的碰撞检测函数
把上面的工具函数和核心逻辑组合起来,得到你需要的最终函数:
function checkRectCollision(svgRect1, svgRect2) { const rect1Vertices = getPolygonVertices(svgRect1); const rect2Vertices = getPolygonVertices(svgRect2); return doPolygonsIntersect(rect1Vertices, rect2Vertices); }
使用方法
假设你有两个SVG矩形元素,比如:
<rect id="rect1" x="50" y="50" width="100" height="50" transform="rotate(30 100 75)" /> <rect id="rect2" x="150" y="80" width="80" height="60" />
你可以这样调用检测:
const rect1 = document.getElementById('rect1'); const rect2 = document.getElementById('rect2'); const isColliding = checkRectCollision(rect1, rect2); console.log(isColliding); // 输出true或false
这个方法的优势在于:它完全考虑了元素的旋转、平移、缩放等所有变换,检测精度极高,而且不仅适用于矩形,任何SVG凸多边形(比如<polygon>、<path>)都可以用这个逻辑来检测碰撞。
内容的提问来源于stack exchange,提问作者Jack Buckley
相关产品推荐
相关产品推荐

