## 用户交互 没有用户交互的动画就跟电视上的动画片一样,不管谁看,都是一个样,千年不变。显然,这不是我们想要的,很多时候,我们需要用户参与进来,这样才能产生丰富的动画效果,这就是专门用一章来了解用户交互的原因。 用户交互是基于用户事件的,这些事件通常包括鼠标事件、键盘事件以及触摸事件。 **1、事件绑定和事件取消** 在这里,并不会对JavaScript事件做过多的解析,如需详细了解,可看这里:《[JavaScript学习笔记整理(10):Event事件](http://ghmagical.com/article/page/id/nXCnaSLsuyWd)》。 由于我们无法获取到canvas上绘制的线和形状,所以只能在canvas上绑定事件,然后根据鼠标相对canvas的位置而确定在哪个线或形状上,比如要为canvas绑定鼠标点击(mousedown)事件: ``` function MClick(event){ console.log('鼠标点击了canvas'); }; canvas.addEventListener('mousedown',MClick,false); ``` 当鼠标在canvas上点击时,每次都会在控制台打印出"鼠标点击了canvas"。 我们使用removeEventListener()还可以取消鼠标点击事件: ``` canvas.removeEventListener('mousedown',MClick,false); ``` 上面的代码就取消了canvas上的鼠标点击事件,不过要注意的是,这里传入的只能是函数名,而不能传入函数体,而且只是取消了鼠标点击事件中的MClick事件处理函数,如果还绑定了其他的鼠标点击事件,依然有效。 **2、鼠标事件** 鼠标事件有很多种,常见的有下面这些: ``` mousedown mouseup click dblclick mousewheel mousemove mouseover mouseout ``` 当为元素注册一个鼠标事件处理函数时,它还会为函数传入一个MouseEvent对象,这个对象包含了多个属性,比如我们接下来要用的`pageX`和`pageY`。 `pageX` 和 `pageY` 分别是触点相对HTML文档左边沿的X坐标和触点相对HTML文档上边沿的Y坐标。只读属性。 注意:当存在滚动的偏移时,pageX包含了水平滚动的偏移,pageY包含了垂直滚动的偏移。 显然,通过pageX和pageY获取到的只是相对于HTML文档的鼠标位置,并不是我们想要的,我们需要的是相对于canvas的鼠标位置,如何得到呢? 只需用pageX和pageY分别减去canvas元素的左偏移和上偏移距离就可得到相对canvas的鼠标位置: ``` canvas.addEventListener('mousedown',function(event){ var x = (event.pageX || event.clientX + document.body.scrollLeft +document.documentElement.scrollLeft) - canvas.offsetLeft; var y = (event.pageY || event.clientY + document.body.scrollTop +document.documentElement.scrollTop) - canvas.offsetTop; },false); ``` 在上面的代码中,还使用了clientX和clientY,这是为了兼容不同的浏览器。 注意:这里的canvas偏移位置是相对HTML文档的。 为了避免后面重复写代码,我们先来造个轮子,创建一个tool.js文件(后续都会用到),在里面创建一个全局对象tool,然后将需要的方法传入进去: ``` window.tool = {}; window.tool.captureMouse = function(element,mousedown,mousemove,mouseup){ /*传入Event对象*/ function getPoint(event){ event = event || window.event; /*为了兼容IE*/ /*将当前的鼠标坐标值减去元素的偏移位置,返回鼠标相对于element的坐标值*/ var x = (event.pageX || event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft); x -= element.offsetLeft; var y = (event.pageY || event.clientY + document.body.scrollTop + document.documentElement.scrollTop); y -= element.offsetTop; return {x:x,y:y}; }; if(!element) return; /*为element元素绑定mousedown事件*/ element.addEventListener('mousedown',function(event){ event.point = getPoint(event); mousedown && mousedown.call(this,event); },false); /*为element元素绑定mousemove事件*/ element.addEventListener('mousemove',function(event){ event.point = getPoint(event); mousemove && mousemove.call(this,event); },false); /*为element元素绑定mouseup事件*/ element.addEventListener('mouseup',function(event){ event.point = getPoint(event); mouseup && mouseup.call(this,event); },false); }; ``` 轮子已经造好了,使用方法也很简单: ``` /*回调函数会传入一个event对象,event.point中包含了x和y属性,分别对应鼠标相对element的X坐标和Y坐标,函数内的this指向绑定元素element*/ function mousedown(event) { console.log(event.point.x,event.ponit.y); console.log(this); document.querySelector('.pointX').innerHTML = event.point.x; document.querySelector('.pointY').innerHTML = event.point.y; }; function mousemove(event) { console.log(event.point); document.querySelector('.pointX1').innerHTML = event.point.x; document.querySelector('.pointY1').innerHTML = event.point.y; var x = event.point.x; var y = event.point.y; var radius = 5; /*清除整个canvas画布*/ ctx.clearRect(0,0,canvas.width,canvas.height); ctx.fillStyle = 'red'; ctx.beginPath(); /*绘制一个跟随鼠标的圆*/ ctx.arc(x,y,radius,0,2*Math.PI,true); ctx.fill(); ctx.closePath(); }; function mouseup(event) { console.log(event.point); document.querySelector('.pointX2').innerHTML = event.point.x; document.querySelector('.pointY2').innerHTML = event.point.y; }; /*传入canvas元素,后面是传入三个函数,分别对应mousedown、mousemove和mouseup事件的事件处理函数*/ tool.captureMouse(canvas, mousedown, mousemove, mouseup); ``` 上面代码中的mousedown、mousemove和mouseup三个方法都是自定义的,三个回调函数都会传入一个event对象(element当前绑定事件的event对象), event.point 是我定义的,它包含了 x 和 y 属性,分别对应鼠标相对element(也就是元素左上角)的X坐标和Y坐标,函数内的 this 指向绑定元素element。 实例(获取鼠标坐标):canvas-demo/captureMousePoint.html **3、触摸事件** 常用触摸事件: touchstart touchmove touchend 触摸事件和鼠标事件最大的区别在于,触摸有可能会在同一时间有多个触摸点,而鼠标永远都是只有一个触摸点。 要获取触摸点相对canvas的坐标,同样是根据event对象中的pageX和pageY,还有canvas相对于HTML文档的偏移位置来确定,不过由于触摸点可能有多个,它传递给事件处理函数的是TouchEvent对象,使用方法稍微有一点区别: ``` canvas.addEventListener('touchstart',function(event){ var touchEvnet = event.changedTouches[0]; var x = (touchEvent.pageX || touchEvent.clientX + document.body.scrollLeft+ document.documentElement.scrollLeft ); x -= canvas.offsetLeft; var y = (touchEvent.pageY || touchEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop ); y -= canvas.offsetTop; }); ``` 注意上面代码中的 event.changedTouches[0] ,这是获取当前触摸事件引发的所有Touch对象中第一个触摸点的Touch对象,当然还有 event.touches[0] 也可以获取到,它是获取所有仍然处于活动状态的触摸点中的第一个。 对于触摸事件,我们也可以在tool这个轮子里添加一个captureTouch方法,你可以用手机或者打开浏览器控制台模拟手机模式看看这个例子:触摸例子(canvas-demo/captureTouchPoint.html) ``` window.tool.captureTouch = function(element,touchstart,touchmove,touchend){ /*传入Event对象*/ function getPoint(event){ event = event || window.event; var touchEvent = event.changedTouches[0]; /*将当前的鼠标坐标值减去元素的偏移位置,返回鼠标相对于element的坐标值*/ var x = (touchEvent.pageX || touchEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft); x -= element.offsetLeft; var y = (touchEvent.pageY || touchEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop); y -= element.offsetTop; return {x:x,y:y}; }; if(!element) return; /*为element元素绑定touchstart事件*/ element.addEventListener('touchstart',function(event){ event.point = getPoint(event); touchstart && touchstart.call(this,event); },false); /*为element元素绑定touchmove事件*/ element.addEventListener('touchmove',function(event){ event.point = getPoint(event); touchmove && touchmove.call(this,event); },false); /*为element元素绑定touchend事件*/ element.addEventListener('touchend',function(event){ event.point = getPoint(event); touchend && touchend.call(this,event); },false); }; ``` 下面我会将鼠标事件和触摸事件结合在一起,添加一个captureMT方法: ``` window.tool.captureMT = function(element, touchStartEvent, touchMoveEvent, touchEndEvent) { 'use strict'; var isTouch = ('ontouchend' in document); var touchstart = null; var touchmove = null var touchend = null; if(isTouch){ touchstart = 'touchstart'; touchmove = 'touchmove'; touchend = 'touchend'; }else{ touchstart = 'mousedown'; touchmove = 'mousemove'; touchend = 'mouseup'; }; /*传入Event对象*/ function getPoint(event) { /*将当前的触摸点坐标值减去元素的偏移位置,返回触摸点相对于element的坐标值*/ event = event || window.event; var touchEvent = isTouch ? event.changedTouches[0]:event; var x = (touchEvent.pageX || touchEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft); x -= element.offsetLeft; var y = (touchEvent.pageY || touchEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop); y -= element.offsetTop; return {x: x,y: y}; }; if(!element) return; /*为element元素绑定touchstart事件*/ element.addEventListener(touchstart, function(event) { event.point = getPoint(event); touchStartEvent && touchStartEvent.call(this, event); }, false); /*为element元素绑定touchmove事件*/ element.addEventListener(touchmove, function(event) { event.point = getPoint(event); touchMoveEvent && touchMoveEvent.call(this, event); }, false); /*为element元素绑定touchend事件*/ element.addEventListener(touchend, function(event) { event.point = getPoint(event); touchEndEvent && touchEndEvent.call(this, event); }, false); }; ``` 在上面的代码中,我们先检测是移动还是PC('ontouchend' in document),然后将布尔值传给变量 isTouch ,然后定义使用mouse事件还是touch事件,获取坐标点时,也是根据 isTouch的值来绝对直接用 event还是用 event.changedTouches[0] 。 **4、键盘事件** 键盘事件只有三个: keydown 按下键盘时触发该事件。 keyup 松开键盘时触发该事件。 keypress 只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件(较少用到)。 只要用户一直按键不松开,就会连续触发键盘事件,触发顺序如下: ``` keydown keypress keydown keypress (重复以上过程) keyup ``` 在这里,我们只来了解keydown和keyup就足够了。 我们会将事件绑定到window上,而不是canvas上,因为如果将键盘事件绑定到某个元素上,那么该元素只有在获取到焦点时才会触发键盘事件,而绑定到window上时,我们可以任何时候监听到。 一般来说,我们比较关心键盘上的箭头按钮(上下左右): ``` function keyEvent(event){ switch (event.keyCode){ case 38: keybox.innerHTML = '你点击了向上箭头(↑)'; break; case 40: keybox.innerHTML = '你点击了向下箭头(↓)'; break; case 37: keybox.innerHTML = '你点击了向左箭头(←)'; break; case 39: keybox.innerHTML = '你点击了向右箭头(→)'; break; default: keybox.innerHTML = '你点击了其他按钮'; }; }; window.addEventListener('keydown',keyEvent,false); ``` String.fromCharCode(e.keyCode); 为了便利,我将keydown和keyup事件封装成这样: ``` window.tool.captureKeyDown = function(params) { function keyEvent(event) { params[event.keyCode](); }; window.addEventListener('keydown', keyEvent, false); }; window.tool.captureKeyUp = function(params) { function keyEvent(event) { params[event.keyCode](); }; window.addEventListener('keyup', keyEvent, false); }; ``` 需要时只需如下调用: ``` function keyLeft(){ keybox.innerHTML = '你点击了向左箭头(←)'; }; function keyRight(){ keybox.innerHTML = '你点击了向右箭头(→)'; }; window.tool.captureKeyEvent({"37":keyLeft,"39":keyRight}); ``` 传入一个键值对形式的对象,键名是键码,键值是调用函数。 实例(先点击下面,让其获取到焦点): canvas-demo/keyboard.html 在后面会有个附录,有完整的键码值对应表。不过,我们不需要去死记硬背,你可以使用到再去查或者使用插件 keycode.js(可到这里下载:https://github.com/lamberta/html5-animation): ``` <script src="keycode.js"></script> <script> function keyEvent(event){ switch (event.keyCode){ case keycode.UP: keybox.innerHTML = '你点击了向上箭头(↑)'; break; }; }; window.addEventListener('keydown',keyEvent,false); </script> ``` 其实keycode.js里定义了一个全局变量keycode,然后以键值对的形式定义键名和键名值。 **总结** 用户交互在游戏动画中是很重要的一步,所以掌握用户交互的各种事件是必须的,而且特别强调一点是,要学会制造轮子,避免重复的编写相同的代码,不过,初学者建议多敲 。 附录: ![](https://box.kancloud.cn/4f4141060d2bc8c44739c7f01af1d650_800x1011.jpg)