## 用戶交互
沒有用戶交互的動畫就跟電視上的動畫片一樣,不管誰看,都是一個樣,千年不變。顯然,這不是我們想要的,很多時候,我們需要用戶參與進來,這樣才能產生豐富的動畫效果,這就是專門用一章來了解用戶交互的原因。
用戶交互是基于用戶事件的,這些事件通常包括鼠標事件、鍵盤事件以及觸摸事件。
**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,然后以鍵值對的形式定義鍵名和鍵名值。
**總結**
用戶交互在游戲動畫中是很重要的一步,所以掌握用戶交互的各種事件是必須的,而且特別強調一點是,要學會制造輪子,避免重復的編寫相同的代碼,不過,初學者建議多敲 。
附錄:
