决战canvas中国象棋设计之动画基础(连载二)
Canvas做动画与普通JS控制的动画有所不同。本篇着重介绍其中的特殊之处,并实现中国象棋的棋子拖动及撤回动画。
普通JS动画一般只需要控制一些页面元素,改变它们的位置或透明度、色彩等属性,其余的浏览器会帮我们完成。但到了Canvas里面,所有绘制出来的图像元素,并不是各自独立的DOM节点。它们共同做为一个Canvas节点存在。而我们的动画则要求Canvas里的图像元素发生运动。
摆在面前的障碍就是,我们无法像控制DOM节点那样直接控制图像元素的位置等属性。
那么Canvas怎么做动画呢?答案只有一个:不停地重绘。你无法用style.left之类的属性控制图像元素,但你可以设定自己的变量,并利用这些变量绘制图形。这样,只要变量在变,不断重绘的结果就形成了我们所需的动画。
不要对重绘感到恐惧。重绘普遍存在于显示技术中。即使你用JS控制DOM节点进行移动,实际上浏览器也在悄悄的用重绘来实现。
让我们结合上一篇的例子,略做改进,给棋子加上鼠标事件(加在Canvas节点上,通过坐标判断来确定点击等事件是否发生在棋子上),实现棋子的拖动效果。
onmousedown与onmouseup是拖拽的开始与结束事件,我们在这两个事件里所需做的主要工作是进行拖拽状态的标记。onmousemove是拖拽的具体实现,也就是在这里进行重绘。来看下代码:
if (chess.isDragging != true) return; var pos = fixMousePoint(e, canvasNode); chess.offsetRect.moveTo(pos.x - layout.cell / 2, pos.y - layout.cell / 2); drawBoard(ctx); chess.drawChess(ctx);
这里首先判断是否处于拖拽状态,如果不是则不做处理。对拖拽的处理也很简单,首先取出鼠标位置;然后更新棋子的offsetRect对象,使之指向当前鼠标位置;最后进行重绘,先绘棋盘再绘棋子(这里为演示方便只使用了一个棋子)。
怎么样,是不是棋子已经能跟随鼠标移动了?是不是挺简单的?当然很简单,本系列的深度内容都在后面,这篇及前篇只是开路先锋。
对于非鼠标拖拽的运动效果,只需要将onmousemove换成setInterval就可以了,道理就是这么简单。不过,让我们把效果玩得更好一些吧。
你一定见过很多动感十足的运动效果,有没有研究过它们的规律呢?比如我们想实现拖拽结束后,让棋子潇洒地飞回原地,这个动作怎么样实现呢?细心的人可能已经发现,多数很酷的滑动效果都是用缓来实现的。缓动也就是,速度从慢到快变化。当然也可以反过来,更可以组合。我们将计划实现一个速度从慢到快再到慢的棋子返回原地的动画。
如何达到这种平滑的速度变化呢?你可以自定义规则来实现,但推荐你在脑海里搜索一下各种数学函数,这些函数真的很优美,也很简单,像我这样把数学还给老师的人也能看懂。最常用的动画函数是幂函数,常用的几种如下图所示:
观察一下x, y都大于零的时候,四个函数的特点。如果把x看作时间,把y看作位移,则y=x时是匀速运动,其它指数为正整数的情况下,速度都是先慢后快,并且指数越高,速度变化幅度越大。
如果我们要实现慢速启动和缓慢停止,则可以将一正一反两个函数拼接起来。有了这些基础概念,剩下的就是将这个函数映射到实际问题中了,只需要“放大”或“缩小”,使选定的图像片段调整到和我们需要的运动距离及时间相对应,就可以实现我们的目的了。
让我们在onmouseup里添加处理,实现缓动画效果:
canvasNode.onmouseup = function(e) { if (chess.isDragging) { chess.isDragging = false; moveChessBack(); } } function moveChessBack() { var left = layout.padding + layout.cell * chess.pos.x–layout.cell / 2, top = layout.padding + layout.cell * chess.pos.y–layout.cell / 2; var dx = left–chess.offsetRect.left, dy = top–chess.offsetRect.top; var t = 0, c = 20; var timer = setInterval(function() { if (++t > c) { clearInterval(timer); chess.offsetRect.moveTo(left, top); return; } var ratio = 0; if (t <= c / 2) { ratio = 2 * t / c; ratio = 1–0.5 * Math.pow(ratio, 4); } else { ratio = 2–2 * t / c; ratio = 0.5 * Math.pow(ratio, 4); } chess.offsetRect.moveTo(left–dx * ratio, top–dy * ratio); drawBoard(ctx); chess.drawChess(ctx); }, 40); }
看看效果吧,只可惜截图是死的,做视频太烦,随后我把代码附上来供大家参考。本篇解决了基本的动画处理问题,基于这些知识,大家可以发散思维,添枝加叶,让动画更酷些。笔者的示例中改变了正在拖动的棋子的阴影位置,以示区分。我相信大家会有更多更好的主意,欢迎分享出来。
到这里,Canvas动画的制作原理和棋子的运动处理基本解决了,Canvas技术上制作中国象棋游戏的障碍已经扫清,大道前方,你是否看到了光明?哦,不一定,在“花明”之前,总会有些“柳暗”,不知你是否感觉得到。不过没关系,无论世界是黑是白,我们心中要有一个信念,就像尤达大师说的,原力的光明面远比黑暗面强大。对错不重要,把它当做一种信仰就好。