马黑黑 发表于 2022-9-6 12:53

在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 12:53

本帖最后由 马黑黑 于 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>

红影 发表于 2022-9-6 19:41

按照范例中给出的数值,落在半径55范围内的全部变成手型,之外的默认。
同时进一步判断,在半径小于45的,出现内圆字样,用排除法得到环字样,用上述全部排除法得到环外字样。

这个编程很巧妙,还以为要一个个判断,实际是找出全部环,只要判断内圆就行了,其他的就连续两个 else就解决了,非常赞{:4_199:}

红影 发表于 2022-9-6 19:44

有一点没看懂,为什么let cc = {
      x: 1*track.getAttribute('cy'), 感觉应该是cx吧,虽然例子中的xy相等,若是不等的时候,结果会有问题的吧?

马黑黑 发表于 2022-9-6 21:28

红影 发表于 2022-9-6 19:44
有一点没看懂,为什么let cc = {
      x: 1*track.getAttribute('cy'), 感觉应该是cx吧,虽然例子中的x ...

对,应该是cx,写错了。已经修改。

红影 发表于 2022-9-6 22:12

马黑黑 发表于 2022-9-6 21:28
对,应该是cx,写错了。已经修改。

“这些值都乘以 1 的作用在于确保所得到的值均为数值型类型,因为 element.getAttribute() 方法提供的值是字符串类型。”
用这方法就能保证得到的是数值类型,这个倒没想到。

马黑黑 发表于 2022-9-7 09:02

红影 发表于 2022-9-6 22:12
“这些值都乘以 1 的作用在于确保所得到的值均为数值型类型,因为 element.getAttribute() 方法提供的值 ...

我先用 console.log 显示获得的值,数值都放引号里,说明是字符串值,所以要强制为数值,否则参与计算时可能出错(特别是出现两个变量相加时,JS会认为是字符串拼接)。

加林森 发表于 2022-9-7 12:08

来学习!

红影 发表于 2022-9-7 16:30

马黑黑 发表于 2022-9-7 09:02
我先用 console.log 显示获得的值,数值都放引号里,说明是字符串值,所以要强制为数值,否则参与计算时 ...

哦哦,是的,值的属性必须被定义。

马黑黑 发表于 2022-9-7 17:28

红影 发表于 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 22:54

马黑黑 发表于 2022-9-7 17:28
JS对数据类型不严格,正因为此特性,有时候会出错。假设a是字符串类型,b也是字符串类型,则:

let a...

我的天,还有这么奇葩的结果啊。{:4_173:}

马黑黑 发表于 2022-9-7 23:20

红影 发表于 2022-9-7 22:54
我的天,还有这么奇葩的结果啊。

所以,不系统学习,或不尝试过,容易搞错

红影 发表于 2022-9-8 10:13

马黑黑 发表于 2022-9-7 23:20
所以,不系统学习,或不尝试过,容易搞错

还是在尝试中学习更省力,系统学习,听着就很累{:4_173:}

加林森 发表于 2022-9-8 11:44

继续来学习

马黑黑 发表于 2022-9-8 12:06

红影 发表于 2022-9-8 10:13
还是在尝试中学习更省力,系统学习,听着就很累

过了系统学习的时段,只能边做边学

红影 发表于 2022-9-8 21:16

马黑黑 发表于 2022-9-8 12:06
过了系统学习的时段,只能边做边学

边做边学的方式我喜欢{:4_173:}

马黑黑 发表于 2022-9-8 21:36

红影 发表于 2022-9-8 21:16
边做边学的方式我喜欢

但很难全面

红影 发表于 2022-9-9 16:52

马黑黑 发表于 2022-9-8 21:36
但很难全面

不全面就等待下次遇到类似的情形时,再进一步全面呗。

马黑黑 发表于 2022-9-9 17:32

红影 发表于 2022-9-9 16:52
不全面就等待下次遇到类似的情形时,再进一步全面呗。

难以全面。不过也没多少人真正全面

红影 发表于 2022-9-9 20:31

马黑黑 发表于 2022-9-9 17:32
难以全面。不过也没多少人真正全面

只要玩得多,慢慢就完善起来了。被动学习法{:4_173:}
页: [1] 2
查看完整版本: 在svg中检测鼠标指针经过圆与圆环