实现canvas里图形的拖拽
在前端交互中,拖拽是比较常见的。比如页面里弹出的登录框,或者一些个人主页的自定义布局,这种布局允许用户自由的拖拽不同模块到不同位置,当然这种自定义布局涉及到后台数据记录,再或者我们曾经玩过的拼图游戏,这些都是拖拽的应用。
倘若说到拖拽的表现形式,有通过js结合mouse事件(mousedown、mousemove、mouseup)来改变DOM元素的left、top值的,也有html5里拖放API,而今天讲的则是canvas里的图形拖拽。
首先来看实例演示:canvas里图形的拖拽》
那么类似这种交互是如何实现的? 在上篇文章–canvas里的变速(线性)运动中,我们知道,canvas没有DOM的概念,因此就更没有什么给元素绑定事件的做法。我们换个思路,要实现canvas里图形的拖拽,就相当于把图形的绘制坐标不断改变,并且这个绘制坐标是随着鼠标移动而改变的。但如何将鼠标坐标与图形绘制坐标建立关系呢?其实原理和DOM里是一样的。
在DOM中拖拽元素原理大概是这样的:mousedown的时候记录鼠标与元素的距离,这里用offset对象表示:
var offset = { |
接下来是mousemove。试想下,元素随着鼠标移动,鼠标的坐标不断变化,但拖拽过程中鼠标与元素的距离始终保持不变,因此我们可以通过这个距离得出元素的坐标(left、top值):
mouse = { |
这样我们就可以随着鼠标的移动而不断更新元素的坐标。
对于canvas的各种图形,虽然没有left、top值,但是有绘制坐标(类似元素的left、top)。虽然不能绑定事件,但我们可以将事件作用于canvas元素上。鼠标在canvas上移动,我们就清除整个canvas画布,再绘制图形(shape)即可。一些都看起来很顺利:
canvas.addEventListener( "mousedown" , function(e){ |
但是,我们忽略一个很重要的问题:如何确定鼠标是否在某个图形中?
这看起来的确是一个很头疼的问题,因为只有鼠标在图形中,才能更新坐标、重绘图形,产生拖拽的效果。幸运的是,在canvas中提供了一个方法能够检测某个点(坐标)是否在图形中,那就是:isPointInPath,它的参数是坐标点的x和y值,于是就有:
function isMouseInGraph(mouse){ |
当对canvas执行mousedown时,通过isMouseInGraph方法来判断当前鼠标点是否在图形中,然后根据鼠标点与图形的初始距离(上面提到的 offset),在鼠标mousemove移动时,更新图形的绘制坐标,最终实现了canvas的图形拖拽。
对于一些特殊需求,比如canvas中有多个图形,当拖拽到多个图形重叠在一起时,可能会出现多个图形一起移动的情况。但实际上我们想要的效果是,只移动最上面的图形。比如图形A先绘制,图形B后绘制。此时B覆盖在A的上面,在canvas按下鼠标,当鼠标点在A和B的重叠处时,图形A、B都满足isPointInPath方法。但我们需要移动只是图形B,即后面绘制符合条件的图形。此处,我们就可以用一个临时数组来存储这些符合条件的图形对象,取临时数组的最后一项更新它的坐标即可!