为了追求性能,canvas绘制的图像不具备DOM属性,图像不像HTML、svg的子节点那样可以直接进行点击交互。但很多应用场景需要与用户进行细致的交互,比如游戏、音视频播放控制器。谷歌非常重视对画布的应用,其浏览器正在尝试在画布上实现热点功能,所创建的热点API目前处于实验室阶段,需要Root权限方能使用,且非Chromium内核的浏览器尚未支持。所以,就目前而言,要在canvas画布上实现与用户的交互,需要自己解决。本文讨论如何在画布中判断鼠标指针是否在圆内和矩形内,这是一个简单的命题,静态情况下可以轻松实现。
我们先讨论矩形。
canvas画布是一个HTML标签,鼠标指针在其上活动时,我们可以通过 e.offsetX/Y 获取鼠标指针离画布左边缘、上边缘的偏移量,且因canvas画布没有子节点的干扰,e.offsetX/y 总是指向该画布,不会因受到子节点的干扰而产生混乱。而canvas画布在绘制矩形时,已知的数据有矩形的左上角(x,y)坐标、矩形的宽高。现在,已知条件共有六个:
鼠标指针在画布上偏移画布左边缘、上边缘的距离,记为 ① ex,② ey
矩形的左上角(x,y)坐标值,记为 ③ rx,④ ry
矩形的宽高,记为 ⑤ rw,⑥ rh
这六个已知条件足以让我们判断鼠标指针是否在矩形内,判断流程如下:
如果同时满足以下条件——
第一,ex >= rx
第二,ey >= ry
第三,ex <= rx + rw
第四,ey <= ry + rh
——那么,鼠标指针在矩形之内。
以上对矩形的判断流程应该是好理解的。圆略为复杂一点儿,我们接着来讨论处理方法。这个需要用到勾股定理:圆心已知,xy坐标值分别记作 cx 和 cy,半径已知,记作 r 。设鼠标指针所在点 A 是圆内或圆外的任意一个点,xy坐标值分别记作 ex 和 ey。现在,画 OA 连线,即从圆心 O(cx,cy)画连线到 A(ex,ey),再从 O 出发画指向三点钟方向的水平线 OX,最后从 A 点出发画垂直于 OX 的直线 OQ,OQ 与 OX 相交于 B 点。那么,三角形 OAB 是一个直角三角形,因此,AB2 + OB2 = OA2,此时,我们只需判断 OA 是否大于圆的半径便可。这就简单了,因为三角形的每一个点坐标都是已知的条件—— B 的坐标点,x 为 A 的 ex, y 为圆心 O 的 cy。看下面的推导:
点击点 A 的坐标 :ex, ey
圆心 O 坐标 :cx, cy
B 坐标: ex, cy
↓
OB = ex - cx
AB = ey - cy
OA2 = AB2 + OB2 → OA2 = (ey - cy)2 + (ex - cx)2

拿到点击点到圆心的距离OA,然后和圆的半径 r 比较,问题解决。
下面的演示是根据以上推导逻辑实现,点击画布任意地方,示例均在信息框显示点击处:
点哪儿:尚未点击
示例完整代码:
<script>
let ctx = canv.getContext('2d');
ctx.fillStyle = 'tan';
ctx.strokeStyle = 'gray';
//画圆函数
let drawCircle = (cx,cy,r) => {
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 360 * Math.PI/180);
ctx.fill();
ctx.restore();
};
//画矩形函数
let drawRect = (x,y,w,h) => {
ctx.save();
ctx.fillRect(x,y,w,h);
ctx.restore();
};
//判断是否在圆内函数
let innerCircle = (ex,ey,cx,cy,r) => Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2) <= r;
//判断是否在矩形内函数
let innerRect = (ex,ey,rx,ry,rw,rh) => ex > rx && ey >= ry && ex <= rx + rw && ey < ry + rh;
drawCircle(100, 100, 50); //画圆
drawRect(240, 90, 120, 60); //画矩形
//鼠标点击画布事件
canv.onclick = (e) => {
let msg = innerCircle(e.offsetX, e.offsetY, 100, 100, 50)
? '圆内'
: innerRect(e.offsetX, e.offsetY, 240, 90, 120, 60) ? '矩形内' : '画布空白处'
;
clickMsg.innerText = '点哪儿:' + msg;
};
</script>