canvas動(dòng)畫包教不包會(huì):用戶交互

2018-06-19 15:15 更新

沒有用戶交互的動(dòng)畫就跟電視上的動(dòng)畫片一樣,不管誰看,都是一個(gè)樣,千年不變。顯然,這不是我們想要的,很多時(shí)候,我們需要用戶參與進(jìn)來,這樣才能產(chǎn)生豐富的動(dòng)畫效果,這就是專門用一章來了解用戶交互的原因。


用戶交互是基于用戶事件的,這些事件通常包括鼠標(biāo)事件、鍵盤事件以及觸摸事件。

1、事件綁定和事件取消
在這里,并不會(huì)對(duì)JavaScript事件做過多的解析,如需詳細(xì)了解,可看這里:《JavaScript學(xué)習(xí)筆記整理(10):Event事件》。

由于我們無法獲取到canvas上繪制的線和形狀,所以只能在canvas上綁定事件,然后根據(jù)鼠標(biāo)相對(duì)canvas的位置而確定在哪個(gè)線或形狀上,比如要為canvas綁定鼠標(biāo)點(diǎn)擊(mousedown)事件:

function MClick(event){

  console.log('鼠標(biāo)點(diǎn)擊了canvas');

};


canvas.addEventListener('mousedown',MClick,false);

當(dāng)鼠標(biāo)在canvas上點(diǎn)擊時(shí),每次都會(huì)在控制臺(tái)打印出"鼠標(biāo)點(diǎn)擊了canvas"。

我們使用removeEventListener()還可以取消鼠標(biāo)點(diǎn)擊事件:

canvas.removeEventListener('mousedown',MClick,false);

上面的代碼就取消了canvas上的鼠標(biāo)點(diǎn)擊事件,不過要注意的是,這里傳入的只能是函數(shù)名,而不能傳入函數(shù)體,而且只是取消了鼠標(biāo)點(diǎn)擊事件中的MClick事件處理函數(shù),如果還綁定了其他的鼠標(biāo)點(diǎn)擊事件,依然有效。

2、鼠標(biāo)事件
鼠標(biāo)事件有很多種,常見的有下面這些:

mousedown

mouseup

click

dblclick

mousewheel

mousemove

mouseover

mouseout

當(dāng)為元素注冊一個(gè)鼠標(biāo)事件處理函數(shù)時(shí),它還會(huì)為函數(shù)傳入一個(gè)MouseEvent對(duì)象,這個(gè)對(duì)象包含了多個(gè)屬性,比如我們接下來要用的pageX和pageY:

pageX 和 pageY 分別是觸點(diǎn)相對(duì)HTML文檔左邊沿的X坐標(biāo)和觸點(diǎn)相對(duì)HTML文檔上邊沿的Y坐標(biāo)。只讀屬性。 當(dāng)存在滾動(dòng)的偏移時(shí),pageX包含了水平滾動(dòng)的偏移,pageY包含了垂直滾動(dòng)的偏移。

顯然,通過pageX和pageY獲取到的只是相對(duì)于HTML文檔的鼠標(biāo)位置,并不是我們想要的,我們需要的是相對(duì)于canvas的鼠標(biāo)位置,如何得到呢?

只需用pageX和pageY分別減去canvas元素的左偏移和上偏移距離就可得到相對(duì)canvas的鼠標(biāo)位置:

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偏移位置是相對(duì)HTML文檔的。

為了避免后面重復(fù)寫代碼,我們先來造個(gè)輪子,創(chuàng)建一個(gè)tool.js文件(后續(xù)都會(huì)用到),在里面創(chuàng)建一個(gè)全局對(duì)象tool,然后將需要的方法傳入進(jìn)去:

window.tool = {};   

window.tool.captureMouse = function(element,mousedown,mousemove,mouseup){

 

  /*傳入Event對(duì)象*/

  function getPoint(event){

    event = event || window.event; /*為了兼容IE*/

     /*將當(dāng)前的鼠標(biāo)坐標(biāo)值減去元素的偏移位置,返回鼠標(biāo)相對(duì)于element的坐標(biāo)值*/

    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);

   

};

輪子已經(jīng)造好了,使用方法也很簡單:

/*回調(diào)函數(shù)會(huì)傳入一個(gè)event對(duì)象,event.point中包含了x和y屬性,分別對(duì)應(yīng)鼠標(biāo)相對(duì)element的X坐標(biāo)和Y坐標(biāo),函數(shù)內(nèi)的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;   

  /*清除整個(gè)canvas畫布*/

  ctx.clearRect(0,0,canvas.width,canvas.height);   

  ctx.fillStyle = 'red';   

  ctx.beginPath();

  /*繪制一個(gè)跟隨鼠標(biāo)的圓*/   

  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元素,后面是傳入三個(gè)函數(shù),分別對(duì)應(yīng)mousedown、mousemove和mouseup事件的事件處理函數(shù)*/

tool.captureMouse(canvas, mousedown, mousemove, mouseup);

上面代碼中的mousedown、mousemove和mouseup三個(gè)方法都是自定義的,三個(gè)回調(diào)函數(shù)都會(huì)傳入一個(gè)event對(duì)象(element當(dāng)前綁定事件的event對(duì)象), event.point 是我定義的,它包含了 x 和 y 屬性,分別對(duì)應(yīng)鼠標(biāo)相對(duì)element(也就是元素左上角)的X坐標(biāo)和Y坐標(biāo),函數(shù)內(nèi)的 this 指向綁定元素element。

實(shí)例(獲取鼠標(biāo)坐標(biāo)):


3、觸摸事件
常用觸摸事件:

touchstart

touchmove

touchend

觸摸事件和鼠標(biāo)事件最大的區(qū)別在于,觸摸有可能會(huì)在同一時(shí)間有多個(gè)觸摸點(diǎn),而鼠標(biāo)永遠(yuǎn)都是只有一個(gè)觸摸點(diǎn)。

要獲取觸摸點(diǎn)相對(duì)canvas的坐標(biāo),同樣是根據(jù)event對(duì)象中的pageX和pageY,還有canvas相對(duì)于HTML文檔的偏移位置來確定,不過由于觸摸點(diǎn)可能有多個(gè),它傳遞給事件處理函數(shù)的是TouchEvent對(duì)象,使用方法稍微有一點(diǎn)區(qū)別:

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] ,這是獲取當(dāng)前觸摸事件引發(fā)的所有Touch對(duì)象中第一個(gè)觸摸點(diǎn)的Touch對(duì)象,當(dāng)然還有 event.touches[0] 也可以獲取到,它是獲取所有仍然處于活動(dòng)狀態(tài)的觸摸點(diǎn)中的第一個(gè)。

對(duì)于觸摸事件,我們也可以在tool這個(gè)輪子里添加一個(gè)captureTouch方法,你可以用手機(jī)或者打開瀏覽器控制臺(tái)模擬手機(jī)模式看看這個(gè)例子:觸摸例子

window.tool.captureTouch = function(element,touchstart,touchmove,touchend){

 

  /*傳入Event對(duì)象*/

  function getPoint(event){

    event = event || window.event;

    var touchEvent = event.changedTouches[0];

     /*將當(dāng)前的鼠標(biāo)坐標(biāo)值減去元素的偏移位置,返回鼠標(biāo)相對(duì)于element的坐標(biāo)值*/

    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);

   

};


下面我會(huì)將鼠標(biāo)事件和觸摸事件結(jié)合在一起,添加一個(gè)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對(duì)象*/   

  function getPoint(event) {   

    /*將當(dāng)前的觸摸點(diǎn)坐標(biāo)值減去元素的偏移位置,返回觸摸點(diǎn)相對(duì)于element的坐標(biāo)值*/     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);   

};


在上面的代碼中,我們先檢測是移動(dòng)還是PC('ontouchend' in document),然后將布爾值傳給變量 isTouch ,然后定義使用mouse事件還是touch事件,獲取坐標(biāo)點(diǎn)時(shí),也是根據(jù) isTouch的值來絕對(duì)直接用 event還是用 event.changedTouches[0] 。


實(shí)例(移動(dòng)PC都可以使用):



4、鍵盤事件


鍵盤事件只有三個(gè):

keydown  按下鍵盤時(shí)觸發(fā)該事件。

keyup   松開鍵盤時(shí)觸發(fā)該事件。

keypress  只要按下的鍵并非Ctrl、Alt、Shift和Meta,就接著觸發(fā)keypress事件(較少用到)。

只要用戶一直按鍵不松開,就會(huì)連續(xù)觸發(fā)鍵盤事件,觸發(fā)順序如下:

keydown  

keypress  

keydown  

keypress  

(重復(fù)以上過程)  

keyup

在這里,我們只來了解keydown和keyup就足夠了。

我們會(huì)將事件綁定到window上,而不是canvas上,因?yàn)槿绻麑㈡I盤事件綁定到某個(gè)元素上,那么該元素只有在獲取到焦點(diǎn)時(shí)才會(huì)觸發(fā)鍵盤事件,而綁定到window上時(shí),我們可以任何時(shí)候監(jiān)聽到。


一般來說,我們比較關(guān)心鍵盤上的箭頭按鈕(上下左右):

function keyEvent(event){   

  switch (event.keyCode){   

    case 38:   

      keybox.innerHTML = '你點(diǎn)擊了向上箭頭(↑)';   

      break;   

    case 40:   

      keybox.innerHTML = '你點(diǎn)擊了向下箭頭(↓)';   

      break;   

    case 37:   

      keybox.innerHTML = '你點(diǎn)擊了向左箭頭(←)';   

      break;   

    case 39:   

      keybox.innerHTML = '你點(diǎn)擊了向右箭頭(→)';   

      break;   

    default:   

      keybox.innerHTML = '你點(diǎn)擊了其他按鈕';   

  };   

};   

window.addEventListener('keydown',keyEvent,false);

為了便利,我將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);  

};


需要時(shí)只需如下調(diào)用:

function keyLeft(){   

  keybox.innerHTML = '你點(diǎn)擊了向左箭頭(←)';

};

function keyRight(){   

  keybox.innerHTML = '你點(diǎn)擊了向右箭頭(→)'; 

};

window.tool.captureKeyEvent({"37":keyLeft,"39":keyRight});

傳入一個(gè)鍵值對(duì)形式的對(duì)象,鍵名是鍵碼,鍵值是調(diào)用函數(shù)。


實(shí)例(先點(diǎn)擊下面,讓其獲取到焦點(diǎn)):




在后面會(huì)有個(gè)附錄,有完整的鍵碼值對(duì)應(yīng)表。不過,我們不需要去死記硬背,你可以使用到再去查或者使用插件 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 = '你點(diǎn)擊了向上箭頭(↑)';   

      break;                 

  };   

};   

window.addEventListener('keydown',keyEvent,false);

</script>

其實(shí)keycode.js里定義了一個(gè)全局變量keycode,然后以鍵值對(duì)的形式定義鍵名和鍵名值。


總結(jié)

用戶交互在游戲動(dòng)畫中是很重要的一步,所以掌握用戶交互的各種事件是必須的,而且特別強(qiáng)調(diào)一點(diǎn)是,要學(xué)會(huì)制造輪子,避免重復(fù)的編寫相同的代碼,不過,初學(xué)者建議多敲 。


如有問題,歡迎指正!


附錄:






以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)