## 用户交互
没有用户交互的动画就跟电视上的动画片一样,不管谁看,都是一个样,千年不变。显然,这不是我们想要的,很多时候,我们需要用户参与进来,这样才能产生丰富的动画效果,这就是专门用一章来了解用户交互的原因。
用户交互是基于用户事件的,这些事件通常包括鼠标事件、键盘事件以及触摸事件。
**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)