在svg中检测鼠标指针经过圆与圆环
本帖最后由 马黑黑 于 2022-9-6 21:27 编辑在svg中检测鼠标指针经过圆与圆环 | 马黑黑
我们在《在svg中检测鼠标指针经过圆》帖子中,解决了对鼠标指针滑过圆的监测问题,在此基础上,我们可以实现对圆环的检测。
svg的圆环同样用 circle 指令绘制。试比较如下画圆的语句,第一句画的是圆(前文绘制,用 fill 方法上色),第二句画的是圆环:
<circle cx="100" cy="100" r="50" fill="lightblue" />
<circle cx="100" cy="100" r="50" fill="none" stroke-width="10" stroke="lightblue" />
绘制圆环,如果不设置 fill="none",则圆环之内会用默认颜色填充环内的内圆。绘制圆环同时应定义环的厚度 stroke-width 和描边的颜色 stroke,这样便可以在 svg 中画出漂亮的圆环。
以上只是做圆和圆环画法的比较。我们重新开启炉灶,单纯绘制圆环,并给圆环一个 id 以便后续工作可以对它进行操作,我们还添加一个 text 标签,用以记录鼠标指针的位置信息,帮助我们测试效果:
<svg id="sc" width="200" height="200" style="background: gray">
<circle id="track" cx="100" cy="100" r="50" fill="none" stroke-width="10" stroke="lightblue" />
<text id="movemsg" x="10" y="30" fill="white">位置信息</text>
</svg>
这里,与前文的圆相比,圆环的圆心、半径与之一致,但有一个 10 像素的 stroke-width 值(环的厚度),情形还是发生了变化,不过我们之前根据勾股定理编写的检测函数依然可以用上:
function isCircle(cx, cy, r, ex, ey) {
return Math.pow(ex - cx, 2) + Math.pow(ey - cy, 2) <= Math.pow(r, 2);
}
它依然是我们检测鼠标指针是否经过圆的工具函数,我们要处理的是调用时参数带入的问题。设想是,将圆环视为两个圆:包含圆环在内的圆是大圆,不包含圆环的、被圆环包裹的内圆是小圆,则,如果鼠标指针在大圆之内,再检测鼠标指针是否在小圆内,如果在,就可以获知鼠标指针不在环内,否则鼠标指针在环内。
这样的话,剩下的工作就是弄清大圆和小圆的半径了。stroke-width 值为 10,它就是环的厚度,经检测发现,环的一半往圆外扩展,另一半往圆内伸展,也就是环在 fill 绘制的圆的内外都要一半的地盘。这样,我们就可以确定大圆的半径为 r + stroke-width / 2,小圆的半径为 r - stroke-width / 2,这就OK了。为了调用便捷也为了可能需要的通用性,我们需要建立一个实例化对象,用以装载圆心坐标、半径、环厚度等值,js内置的 element.getAttribute() 方法可以帮助我们获取这些值:
let cc = {
x: 1*track.getAttribute('cx'),
y: 1*track.getAttribute('cy'),
r: 1*track.getAttribute('r'),
sw: 1*track.getAttribute('stroke-width'),
};
track 是我们前面画的圆,我们用 getAttribute() 方法分别获取了圆的圆心坐标、半径和厚度。这些值都乘以 1 的作用在于确保所得到的值均为数值型类型,因为 element.getAttribute() 方法提供的值是字符串类型。下一步,也是最后一步,就是检测 svg 画布 sc 的鼠标经过
sc.onmousemove = (e) => {
if(isCircle(cc.x, cc.y, cc.r + cc.sw/2, e.offsetX, e.offsetY)) { //环及环内的圆
sc.style.cursor = 'pointer';
if(isCircle(cc.x, cc.y, cc.r - cc.sw/2, e.offsetX, e.offsetY)) {
movemsg.textContent = '内圆';
} else {
movemsg.textContent = '环';
}
} else { //环外区域
sc.style.cursor = 'default';
movemsg.textContent = '环外'
}
}
附完整代码:
<svg id="sc" width="200" height="200" style="background: gray">
<circle id="track" cx="100" cy="100" r="50" fill="none" stroke-width="10" stroke="lightblue" />
<text id="movemsg" x="10" y="30" fill="white">位置信息</text>
</svg>
<script>
let cc = {
x: 1*track.getAttribute('cx'),
y: 1*track.getAttribute('cy'),
r: 1*track.getAttribute('r'),
sw: 1*track.getAttribute('stroke-width'),
};
sc.onmousemove = (e) => {
if(isCircle(cc.x, cc.y, cc.r + cc.sw/2, e.offsetX, e.offsetY)) { //环及环内的圆
sc.style.cursor = 'pointer';
if(isCircle(cc.x, cc.y, cc.r - cc.sw/2, e.offsetX, e.offsetY)) {
movemsg.textContent = '内圆';
} else {
movemsg.textContent = '环';
}
} else { //环外区域
sc.style.cursor = 'default';
movemsg.textContent = '环外'
}
}
function isCircle(cx, cy, r, ex, ey) {
return Math.pow(ex - cx, 2) + Math.pow(ey - cy, 2) <= Math.pow(r, 2);
}
</script>
本帖最后由 马黑黑 于 2022-9-6 21:29 编辑 <br /><br /><svg id="sc" width="200" height="200" style="background: gray">
<circle id="track" cx="100" cy="100" r="50" fill="none" stroke-width="10" stroke="lightblue" />
<text id="movemsg" x="10" y="30" fill="white">位置信息</text>
</svg>
<script>
let cc = {
x: 1*track.getAttribute('cx'),
y: 1*track.getAttribute('cy'),
r: 1*track.getAttribute('r'),
sw: 1*track.getAttribute('stroke-width'),
};
sc.onmousemove = (e) => {
if(isCircle(cc.x, cc.y, cc.r + cc.sw/2, e.offsetX, e.offsetY)) { //环及环内的圆
sc.style.cursor = 'pointer';
if(isCircle(cc.x, cc.y, cc.r - cc.sw/2, e.offsetX, e.offsetY)) {
movemsg.textContent = '内圆';
} else {
movemsg.textContent = '环';
}
} else { //环外区域
sc.style.cursor = 'default';
movemsg.textContent = '环外'
}
}
function isCircle(cx, cy, r, ex, ey) {
return Math.pow(ex - cx, 2) + Math.pow(ey - cy, 2) <= Math.pow(r, 2);
}
</script>
按照范例中给出的数值,落在半径55范围内的全部变成手型,之外的默认。
同时进一步判断,在半径小于45的,出现内圆字样,用排除法得到环字样,用上述全部排除法得到环外字样。
这个编程很巧妙,还以为要一个个判断,实际是找出全部环,只要判断内圆就行了,其他的就连续两个 else就解决了,非常赞{:4_199:} 有一点没看懂,为什么let cc = {
x: 1*track.getAttribute('cy'), 感觉应该是cx吧,虽然例子中的xy相等,若是不等的时候,结果会有问题的吧? 红影 发表于 2022-9-6 19:44
有一点没看懂,为什么let cc = {
x: 1*track.getAttribute('cy'), 感觉应该是cx吧,虽然例子中的x ...
对,应该是cx,写错了。已经修改。 马黑黑 发表于 2022-9-6 21:28
对,应该是cx,写错了。已经修改。
“这些值都乘以 1 的作用在于确保所得到的值均为数值型类型,因为 element.getAttribute() 方法提供的值是字符串类型。”
用这方法就能保证得到的是数值类型,这个倒没想到。 红影 发表于 2022-9-6 22:12
“这些值都乘以 1 的作用在于确保所得到的值均为数值型类型,因为 element.getAttribute() 方法提供的值 ...
我先用 console.log 显示获得的值,数值都放引号里,说明是字符串值,所以要强制为数值,否则参与计算时可能出错(特别是出现两个变量相加时,JS会认为是字符串拼接)。 来学习! 马黑黑 发表于 2022-9-7 09:02
我先用 console.log 显示获得的值,数值都放引号里,说明是字符串值,所以要强制为数值,否则参与计算时 ...
哦哦,是的,值的属性必须被定义。 红影 发表于 2022-9-7 16:30
哦哦,是的,值的属性必须被定义。
JS对数据类型不严格,正因为此特性,有时候会出错。假设a是字符串类型,b也是字符串类型,则:
let a = "5", b="2";
console.log(a*b); // → 10
console.log(a + b); // → 52
混合情况,如果a是数值,b是字串,则结果一样:
let a = 5, b="2";
console.log(a*b); // → 10
console.log(a + b); // → 52
所以,必须谨慎,不能掉坑。 马黑黑 发表于 2022-9-7 17:28
JS对数据类型不严格,正因为此特性,有时候会出错。假设a是字符串类型,b也是字符串类型,则:
let a...
我的天,还有这么奇葩的结果啊。{:4_173:} 红影 发表于 2022-9-7 22:54
我的天,还有这么奇葩的结果啊。
所以,不系统学习,或不尝试过,容易搞错 马黑黑 发表于 2022-9-7 23:20
所以,不系统学习,或不尝试过,容易搞错
还是在尝试中学习更省力,系统学习,听着就很累{:4_173:} 继续来学习 红影 发表于 2022-9-8 10:13
还是在尝试中学习更省力,系统学习,听着就很累
过了系统学习的时段,只能边做边学 马黑黑 发表于 2022-9-8 12:06
过了系统学习的时段,只能边做边学
边做边学的方式我喜欢{:4_173:} 红影 发表于 2022-9-8 21:16
边做边学的方式我喜欢
但很难全面 马黑黑 发表于 2022-9-8 21:36
但很难全面
不全面就等待下次遇到类似的情形时,再进一步全面呗。 红影 发表于 2022-9-9 16:52
不全面就等待下次遇到类似的情形时,再进一步全面呗。
难以全面。不过也没多少人真正全面 马黑黑 发表于 2022-9-9 17:32
难以全面。不过也没多少人真正全面
只要玩得多,慢慢就完善起来了。被动学习法{:4_173:}
页:
[1]
2