canvas画布判断鼠标指针是否在圆和矩形内
本帖最后由 马黑黑 于 2024-4-1 12:32 编辑 <br /><br /><style>.ma { font: normal 18px sans-serif; }
.ma p { margin: 12px 0; }
#canv { display: block; margin: 20px auto; border: 1px solid gray; }
#clickMsg { margin: 20px auto; width: 400px; }
.mum { position: relative; margin: 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>
<div class="ma">
<p>为了追求性能,canvas绘制的图像不具备DOM属性,图像不像HTML、svg的子节点那样可以直接进行点击交互。但很多应用场景需要与用户进行细致的交互,比如游戏、音视频播放控制器。谷歌非常重视对画布的应用,其浏览器正在尝试在画布上实现热点功能,所创建的热点API目前处于实验室阶段,需要Root权限方能使用,且非Chromium内核的浏览器尚未支持。所以,就目前而言,要在canvas画布上实现与用户的交互,需要自己解决。本文讨论如何在画布中判断鼠标指针是否在圆内和矩形内,这是一个简单的命题,静态情况下可以轻松实现。</p>
<p>我们先讨论矩形。</p>
<p>canvas画布是一个HTML标签,鼠标指针在其上活动时,我们可以通过 e.offsetX/Y 获取鼠标指针离画布左边缘、上边缘的偏移量,且因canvas画布没有子节点的干扰,e.offsetX/y 总是指向该画布,不会因受到子节点的干扰而产生混乱。而canvas画布在绘制矩形时,已知的数据有矩形的左上角(x,y)坐标、矩形的宽高。现在,已知条件共有六个:</p>
<blockquote>
鼠标指针在画布上偏移画布左边缘、上边缘的距离,记为 ① ex,② ey<br>
矩形的左上角(x,y)坐标值,记为 ③ rx,④ ry<br>
矩形的宽高,记为 ⑤ rw,⑥ rh
</blockquote>
<p>这六个已知条件足以让我们判断鼠标指针是否在矩形内,判断流程如下:</p>
<blockquote>
如果同时满足以下条件——<br><br>
第一,ex >= rx<br>
第二,ey >= ry<br>
第三,ex <= rx + rw<br>
第四,ey <= ry + rh<br><br>
——那么,鼠标指针在矩形之内。
</blockquote>
<p>以上对矩形的判断流程应该是好理解的。圆略为复杂一点儿,我们接着来讨论处理方法。这个需要用到勾股定理:圆心已知,xy坐标值分别记作 cx 和 cy,半径已知,记作 r 。设鼠标指针所在点 A 是圆内或圆外的任意一个点,xy坐标值分别记作 ex 和 ey。现在,画 OA 连线,即从圆心 O(cx,cy)画连线到 A(ex,ey),再从 O 出发画指向三点钟方向的水平线 OX,最后从 A 点出发画垂直于 OX 的直线 OQ,OQ 与 OX 相交于 B 点。那么,三角形 OAB 是一个直角三角形,因此,AB<sup>2</sup> + OB<sup>2</sup> = OA<sup>2</sup>,此时,我们只需判断 OA 是否大于圆的半径便可。这就简单了,因为三角形的每一个点坐标都是已知的条件—— B 的坐标点,x 为 A 的 ex, y 为圆心 O 的 cy。看下面的推导:</p>
<blockquote>
点击点 A 的坐标 :ex, ey<br>
圆心 O 坐标 :cx, cy<br>
B 坐标: ex, cy<br>
↓<br>
OB = ex - cx<br>
AB = ey - cy<br>
OA<sup>2</sup> = AB<sup>2</sup> + OB<sup>2</sup> → OA<sup>2</sup> = (ey - cy)<sup>2</sup> + (ex - cx)<sup>2</sup>
</blockquote>
<p><img alt="" src="https://638183.freep.cn/638183/small/cc.png" /></p>
<p>拿到点击点到圆心的距离OA,然后和圆的半径 r 比较,问题解决。</p>
<p>下面的演示是根据以上推导逻辑实现,点击画布任意地方,示例均在信息框显示点击处:</p>
<div id="clickMsg">点哪儿:尚未点击</div>
<canvas id="canv" width="400" height="200" style=""></canvas>
<p>示例完整代码:</p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="2"> </cl-cd>
<cl-cd data-idx="3"><span class="tBlue">let</span> ctx = canv.getContext(<span class="tMagenta">'2d'</span>);</cl-cd>
<cl-cd data-idx="4">ctx.fillStyle = <span class="tMagenta">'tan'</span>;</cl-cd>
<cl-cd data-idx="5">ctx.strokeStyle = <span class="tMagenta">'gray'</span>;</cl-cd>
<cl-cd data-idx="6"><span class="tGreen">//画圆函数</span></cl-cd>
<cl-cd data-idx="7"><span class="tBlue">let</span> drawCircle = (cx,cy,r) => {</cl-cd>
<cl-cd data-idx="8"> ctx.save();</cl-cd>
<cl-cd data-idx="9"> ctx.beginPath();</cl-cd>
<cl-cd data-idx="10"> ctx.arc(cx, cy, r, 0, 360 * <span class="tRed">Math</span>.PI/180);</cl-cd>
<cl-cd data-idx="11"> ctx.fill();</cl-cd>
<cl-cd data-idx="12"> ctx.restore();</cl-cd>
<cl-cd data-idx="13">};</cl-cd>
<cl-cd data-idx="14"><span class="tGreen">//画矩形函数</span></cl-cd>
<cl-cd data-idx="15"><span class="tBlue">let</span> drawRect = (x,y,w,h) => {</cl-cd>
<cl-cd data-idx="16"> ctx.save();</cl-cd>
<cl-cd data-idx="17"> ctx.fillRect(x,y,w,h); </cl-cd>
<cl-cd data-idx="18"> ctx.restore();</cl-cd>
<cl-cd data-idx="19">};</cl-cd>
<cl-cd data-idx="20"><span class="tGreen">//判断是否在圆内函数</span></cl-cd>
<cl-cd data-idx="21"><span class="tBlue">let</span> innerCircle = (ex,ey,cx,cy,r) => <span class="tRed">Math</span>.sqrt((ex - cx) ** 2 + (ey - cy) ** 2) <= r;</cl-cd>
<cl-cd data-idx="22"><span class="tGreen">//判断是否在矩形内函数</span></cl-cd>
<cl-cd data-idx="23"><span class="tBlue">let</span> innerRect = (ex,ey,rx,ry,rw,rh) => ex > rx && ey >= ry && ex <= rx + rw && ey < ry + rh;</cl-cd>
<cl-cd data-idx="24">drawCircle(100, 100, 50); <span class="tGreen">//画圆</span></cl-cd>
<cl-cd data-idx="25">drawRect(240, 90, 120, 60); <span class="tGreen">//画矩形</span></cl-cd>
<cl-cd data-idx="26"><span class="tGreen">//鼠标点击画布事件</span></cl-cd>
<cl-cd data-idx="27">canv.onclick = (e) => {</cl-cd>
<cl-cd data-idx="28"> <span class="tBlue">let</span> msg = innerCircle(e.offsetX, e.offsetY, 100, 100, 50)</cl-cd>
<cl-cd data-idx="29"> ? <span class="tMagenta">'圆内'</span></cl-cd>
<cl-cd data-idx="30"> : innerRect(e.offsetX, e.offsetY, 240, 90, 120, 60) ? <span class="tMagenta">'矩形内'</span> : <span class="tMagenta">'画布空白处'</span></cl-cd>
<cl-cd data-idx="31"> ;</cl-cd>
<cl-cd data-idx="32"> clickMsg.innerText = <span class="tMagenta">'点哪儿:'</span> + msg;</cl-cd>
<cl-cd data-idx="33">};</cl-cd>
<cl-cd data-idx="34"> </cl-cd>
<cl-cd data-idx="35"><<span class="tDarkRed">/script</span>></cl-cd>
</div>
</div>
<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>
原来人工智能就是这么编程出来的。这个演示太厉害了 。。{:4_199:}
//判断是否在圆内函数
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;
这个高大上的又是等于又是小于又是大于的,完全看不懂呀看不懂。。
看一点能看懂的,烧脑的跳过。。{:4_170:} 南无月 发表于 2024-4-1 18:07
原来人工智能就是这么编程出来的。这个演示太厉害了 。。
人工智能那要大而全,是很多很多很多很多人的智慧的结晶,一个人干不出来的 南无月 发表于 2024-4-1 18:08
//判断是否在圆内函数
let innerCircle = (ex,ey,cx,cy,r) => Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2 ...
一步一步拆分来看,四个同时成立的条件,将条件和式子一一对照就能明白
马黑黑 发表于 2024-4-1 18:10
一步一步拆分来看,四个同时成立的条件,将条件和式子一一对照就能明白
我也是对这个看不懂,对于圆,主要是平方和开根号的命令不熟。对于矩形,对参数含义没太明白。哦,我知道了,ex,ey是设定的任意点,而后面的参数就是相应的圆参数和矩形参数。 let msg = innerCircle(e.offsetX, e.offsetY, 100, 100, 50)符合前面的判断,就输出“圆内”
是不是这个时候,e.offsetX就是参数ex的实际值了? 马黑黑 发表于 2024-4-1 18:08
人工智能那要大而全,是很多很多很多很多人的智慧的结晶,一个人干不出来的
你说的这个是高级人工智能。
我说的是这个演示我点哪儿它居然知道,超级聪明。。
实现过程还被你分析得清清楚楚。。。{:4_170:}
反正看上去很聪明的人工智能,也是按程序走啊走的。。
所以,你说的高级智能也是如此。。 马黑黑 发表于 2024-4-1 18:10
一步一步拆分来看,四个同时成立的条件,将条件和式子一一对照就能明白
这事烧脑,才不干,还是灌水好了。{:4_170:} 南无月 发表于 2024-4-1 20:18
这事烧脑,才不干,还是灌水好了。
矩形的比圆形的容易理解得多,所以我不出图 红影 发表于 2024-4-1 19:53
我也是对这个看不懂,对于圆,主要是平方和开根号的命令不熟。对于矩形,对参数含义没太明白。哦,我知道 ...
里面说的及详细了的。还不能理解的,补一补数学了 看了几遍还是没有明白 红影 发表于 2024-4-1 19:58
let msg = innerCircle(e.offsetX, e.offsetY, 100, 100, 50)符合前面的判断,就输出“圆内”
是不是这个 ...
信息输出不重要,那个只是为了检测。理解里面的原理和算式就行。算式只是代数一样的式子,代码并不重要,代码只是把代数加以实现。 小辣椒 发表于 2024-4-1 21:41
看了几遍还是没有明白
估计数学基础差{:4_170:} 马黑黑 发表于 2024-4-1 18:10
一步一步拆分来看,四个同时成立的条件,将条件和式子一一对照就能明白
//判断是否在圆内函数
let innerCircle = (ex,ey,cx,cy,r) => Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2) <= r;
我拆了。。
let innerCircle拆1:后面这一串成立的话,在圆内
(ex,ey,cx,cy,r)拆2:是A点 O点 半径
Math.sqrt((ex - cx) ** 2 + (ey - cy) ** 2):拆3把 ** 2 理解为平方。。((ex - cx) ** 2 + (ey - cy) ** 2)这串就是OA的平方。。Math.sqrt这句再进行开方,得到OA
<= r;拆4OA这个数据小于等于半径,就是在圆内
{:4_170:}小白乱拆的。。老师瞧瞧
马黑黑 发表于 2024-4-1 21:27
矩形的比圆形的容易理解得多,所以我不出图
代码上面的字都能看个七七八八
就是代码混在一起不好明白。。
老师数学真是好啊。。。啊,佩服极了。 南无月 发表于 2024-4-1 21:59
代码上面的字都能看个七七八八
就是代码混在一起不好明白。。
数学过得去,主要是靠十个手指头加是个脚趾头{:4_170:} 南无月 发表于 2024-4-1 21:56
//判断是否在圆内函数
let innerCircle = (ex,ey,cx,cy,r) => Math.sqrt((ex - cx) ** 2 + (ey - cy) ** ...
就是讲,式子最终的结果是 OA <= r 是否成立
OA你要根据 AB 和 OB 去计算
AB也好,OB也好,其长度要根据两点间的XY坐标去计算 let innerRect = (ex,ey,rx,ry,rw,rh) => ex > rx && ey >= ry && ex <= rx + rw && ey < ry + rh;
let innerRect:拆1:满足后面的条件,在矩形内
(ex,ey,rx,ry,rw,rh):拆2:左边缘,上边缘,左上角的定位点,宽高规定
=> ex > rx 和ey >= ry左边缘和上边缘大于矩形左上角定位点位置时,往矩形里面跑了
ex <= rx + rw 和ey < ry + rh; 跑得位置还要小于矩形左上角定位点加宽度和高度,就是矩形范围之内
就是不能乱跑。。{:4_170:}
马黑黑 发表于 2024-4-1 22:10
数学过得去,主要是靠十个手指头加是个脚趾头
{:4_170:}编程的人好厉害啊。。这得多强的大脑处理器。。