Canvas 本身也属于 HTMLElement,自然也是支持各种事件绑定的。
但绘制在其中的图形并不作为其子元素存在,这就不能方便的为 Canvas 中的某个特定图形去绑定事件。
我们都知道 js 中的事件委托,将事件绑定到父节点上,待到父节点响应事件时,动态判断当前响应元素为目标子节点时再执行对应的操作。
这个思想同样也可以用在 Canvas 上,只需要为 canvas
元素绑定事件,事件响应时判断当前鼠标位置处于哪个图形之上,执行对应的操作。
isPointInPath
context.isPointInPath(x, y);
|
理论上讲,想要知道一个点是否处于一个图形之中,现成的算法应该是有很多了。不过难得 canvas 本身就提供了这样的函数,用来判断一个点是否处于当前路径中。
var c = document.getElementById('canvas'); var ctx = c.getContext('2d'); ctx.rect(0, 0, 200, 200); console.log(ctx.isPointInPath(50, 100))
|
就像这样,当你创建一个矩形时,就会产生一个路径,此时就可以调用该方法去判断一个点是否存在于该路径。
产生路径的函数还有其他,比如:lineTo()
、clip()
、arc()
、arcTo()
等。
实现图形的事件绑定
先来个简单的饼图吧。
<canvas id="c" width="400" height="400"></canvas>
|
var canvas = document.getElementById('c'); var ctx = canvas.getContext('2d'); var r = canvas.width / 2; ctx.beginPath(); ctx.arc(r, r, r, 0, Math.PI * 1); ctx.fillStyle = '#2196f3'; ctx.fill(); ctx.beginPath(); ctx.arc(r, r, r, Math.PI * 1, Math.PI * 2); ctx.fillStyle = '#f44336'; ctx.fill(); function isInPath (x, y){ ctx.arc(r, r, r, 0, Math.PI * 1); return ctx.isPointInPath(x, y); } canvas.addEventListener('click', function(e){ if(isInPath(e.offsetX, e.offsetY)) { console.log('hello') } })
|
现在创建一个红蓝拼接的饼图,isInPath
方法判断一个点是否处于蓝色区。理想的结果是只有当鼠标点击区域为蓝色区域时才输出 hello
。
但事实确不是如此,示例Demo。无论点击红色还是蓝色区域均会输出 hello
,这是怎么回事呢?
路径
既然 isPointInPath(x, y)
的基于路径判断的,那我们就从路径入手。
ctx.arc(r, r, r, 0, Math.PI * 1); ctx.fillStyle = '#2196f3'; ctx.fill(); ctx.arc(r, r, r, Math.PI * 1, Math.PI * 2); ctx.fillStyle = '#f44336'; ctx.fill();
|
当我们把画图时的 ctx.beginPath()
去掉后,发现生成的图形变成一个红色的整圆了:示例Demo。
beginPath()
用来重置路径,由于第一个半圆画完路径未重置,第二个半圆就绘制了两条路径。这似乎解释了上个问题的答案。
在 isInPath(x, y)
函数中,由于路径没有重置,所以最终最终判断的不止是 ctx.arc(r, r, r, 0, Math.PI * 1)
这个路径,还有方法外的画红色圆的路径。两个路径加一起自然就是个整圆,所以无论蓝色区还是红色区都会输出。
正确结果
function isInPath (x, y){ ctx.beginPath(); ctx.arc(r, r, r, 0, Math.PI * 1); return ctx.isPointInPath(x, y) }
|
修改 isInPath
函数,加入重置路径,结果正确输出:示例Demo。
实战示例
鼠标悬浮显示区块数据的饼图。