用户交互

用户交互

没有用户交互的动画就跟电视上的动画片一样,不管谁看,都是一个样,千年不变。显然,这不是我们想要的,很多时候,我们需要用户参与进来,这样才能产生丰富的动画效果,这就是专门用一章来了解用户交互的原因。

用户交互是基于用户事件的,这些事件通常包括鼠标事件、键盘事件以及触摸事件。

1、事件绑定和事件取消

在这里,并不会对JavaScript事件做过多的解析,如需详细了解,可看这里:《JavaScript学习笔记整理(10):Event事件》。

由于我们无法获取到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对象,这个对象包含了多个属性,比如我们接下来要用的pageXpageY

pageXpageY 分别是触点相对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,然后以键值对的形式定义键名和键名值。

总结

用户交互在游戏动画中是很重要的一步,所以掌握用户交互的各种事件是必须的,而且特别强调一点是,要学会制造轮子,避免重复的编写相同的代码,不过,初学者建议多敲 。

附录:

文章导航