应使用AABB算法进行Canvas矩形碰撞检测,而非getBoundingClientRect();因后者受transform、滚动、iframe影响易偏移,需手动校正且易出错,而AABB直接基于Canvas坐标系的x、y等属性计算,稳定可靠。
getBoundingClientRect()
Canvas 里做矩形碰撞,最常用也最稳的是轴对齐边界框(AABB)算法,不是靠 getBoundingClientRect() 拿 DOM 坐标——那玩意儿快是快,但一遇上 transform、滚动、iframe 就偏移失真,得手动校正,反而容易出错。
你该直接用 Canvas 坐标系下的对象属性:每个矩形有 x、y、、<code>height,判断逻辑就一行:
function isRectCollision(a, b) { return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y);}
a.x 这类正向条件,容易漏掉等号边界(比如刚好贴边时误判)
Math.hypot(),手算 dx*dx + dy*dy 更安全两个圆是否碰撞,只看圆心距离是否小于半径和。但 Math.hypot(dx, dy) 在旧版 Safari 和部分 Electron 环境里不支持,而且开方本身有浮点误差和性能开销。
直接平方比较,既快又稳:
立即学习“前端免费学习笔记(深入)”;
function isCircleCollision(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; const rSum = a.radius + b.radius; return dx * dx + dy * dy < rSum * rSum;}
< 而不是 <=,因为“刚好相切”通常不算碰撞(除非你的游戏逻辑明确需要)radius 不能直接取渲染尺寸——得用原始定义半径,否则缩放后会误判比如小球撞墙、角色撞箱子,不能套用纯矩形或纯圆形逻辑。核心是:先求圆心在矩形边界上的投影点(即“最近点”),再测该点到圆心的距离是否 ≤ 半径。
function isAABBCircleCollision(rect, circle) { const clampedX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width)); const clampedY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height)); const dx = circle.x - clampedX; const dy = circle.y - clampedY; return dx * dx + dy * dy <= circle.radius * circle.radius;}
clampedX/Y 是把圆心 x/y “拉回”矩形范围的结果:在内就是圆心本身,在外就是对应边上的垂足scale() 或 translate() 影响;如果用了 canvas 变换,得先反向变换圆心坐标当你要检测带透明边缘的角色图、不规则地形贴图是否真正“像素相触”,就得走 getImageData()。但它有两个硬限制:一是跨域图片会触发 canvas 污染(SecurityError),二是全图读取太慢。
CORS 头,或前端加载时设 img.crossOrigin = 'anonymous'
ctx.getImageData(0, 0, canvas.width, canvas.height) —— 一次读几 MB 图像数据,帧率直接崩ctx.getImageData(x, y, width, height),其中 x/y/width/height 是交集矩形的 Canvas 坐标这一步几乎总是性能瓶颈,上线前务必用 console.time() 测真实耗时——尤其在低端 Android 设备上,10×10 像素交集都可能卡顿。