之前的学习已经让我们知道如何使用canvas呈现按照要求定时刷新的“anker钟”,但是无论是展示图片或者文字,亦或者定时刷新都是事先设定好的,但是实际使用时,我们通常希望图形界面是可以交互的,本章主要讲的就是Canvas的事件体系。
给Canvas绑定一个事件
第一回我们就讲到过Canvas是一个特殊的HTML元素,其特殊性主要是在于其内部是一个“画布”,而就元素本身,其实它与其它HTML元素并无区别,所以我们可以很简单的通过dom.addEventListener就可以方便的给canvas注册一个事件监听器。
但是如果我们的交互事件需要影响到Canvas的内部,那么事情就会复杂起来,首先我们回忆一下我们之前讲到过,Canvas自己本身的宽高与canvas元素样式之间的关系,当其相等时那么就是1:1的展示,当出现不匹配时,浏览器会做出对应的缩放,这就导致了canvas绘图表面与预计的大小可能不一致的情况,特别是在做一些鼠标追踪的交互时,这个影响将变得无比棘手,所以为了应付这一问题,我们通常需要通过网页的坐标向canvas坐标的转换,从而达到获知画布内鼠标对应的位置,并通过获取像素点的信息,来达到交互的目的。 如下所示如果我想知道鼠标活动的相对于canvas的绘图表面的位置,就像下面这个例子:
上面是个很长的问题,归根结底无非就是要问如何获取绘图表面中鼠标相对于左上角的位置,正如我们所知,其实我们通过对任意的dom元素绑定鼠标事件,在事件触发的event对象中,我们通常可以获取clientX与clientY相对于页面左上角的定位,那么这样的方式对于canvas同样成立,但是光获取鼠标相对于页面左上角的位置是不够的,我们还需要知道鼠标在绘图表面上所处的位置,那么我们就会要用到下面说到的方法,将页面的坐标转换为canvas内的坐标:
/**
* @param canvas 为获取的canvas的dom实例
* @param clientX EventObj.clientX
* @param clientY EventObj.clientY
*/
function winCoordinate2CanvasCoordinate(canvas, clientX, clientY){
let clientRect = canvas.getBoundingClientRect();
return {
x: (clientX - clientRect.left) * (canvas.width / clientBox.width),
y: (clientY - clientRect.top) * (canvas.height / canvas.height),
}
}
乍一看可能不明白为啥又要减,又要乘除的,一头雾水,咱们稍微理清下思路,首先入参已经将事件触发时的clientX,clientY返回给了我们,那么我们可以想象一下下面这个图片:
|———body——————————————-|
| | |
| | |
| |------------|-------canvas—| |
|-----------------e | |
| | | |
| | | |
| |-----------------------------| |
| |
|———————————————————-|
e为事件触发的位置,除了代表body和canvas边框的虚线之外多出的两条虚线就是clientX与clientY。以x为例,其中是clientX是可以分解为两段距离长度,一段是canvas距离body的左边距的长度,一段是canvas内部的长度,所以就得到了算绘图表面x的第一段,通过用clientX减去canvas的左边距就可以得到在window坐标体系下e距离canvas左边距的距离,然后我们可以通过简单的等比关系:
绘图表面距离 = 绘图表面宽度 / canvas元素宽度 * window距离
那么公式已经列出来了,接下来我们只需要查询下api就知道获取canvas的外框的参数即可,获取left,获取width,如代码所示我们用的就是canvas实例自带的方法getBoudingClientRect(),就是示意图上写着canvas的虚线方框,这个就是canvas提供获取边界框的方法,我们可以从返回的对象上获得边界框的left和width。
那么我们就可以得到如上示例代码中的计算公式了,y的换算方法也是一致的,原书的作者估计是没有仔细验证自己的代码,没有添加(clientX - clientRect.left)的这一括号,导致当真的出现缩放的时候,其计算就会出现问题,这也就应证了尽信书则不如无书。