《HTML5+JavaScript动画基础》——2.5 用户交互-阿里云开发者社区

开发者社区> 异步社区> 正文

《HTML5+JavaScript动画基础》——2.5 用户交互

简介: 该函数接受一个DOM元素作为参数并为之添加一个mousemove事件的处理程序,最终返回一个包含x与y属性的对象。当鼠标在该元素上移动时,处理程序会根据事件发生的位置(每个浏览器提供不同支持)以及该元素相对于文件的偏移量计算出鼠标在元素上的相对位置,然后将表示位置的两个值赋值给作为函数返回值的对象,最终可以在调用该函数的主脚本中访问它们。
+关注继续查看

本节书摘来自异步社区《HTML5+JavaScript动画基础》一书中的第2章,第2.5节,作者:【美】Billy Lamberta , Keith Peters著,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.5 用户交互

用户交互可能是你选择本书的一个重要原因。毕竟,如果没有交互或者无法对动画产生一些动态的影响,你可能就去看电影了。用户交互是基于用户事件的,这些事件通常是鼠标事件、触摸事件以及键盘事件。让我们快速地浏览各种用户事件类型以及如何处理这些事件。

2.5.1 事件与事件处理程序
为了理解事件,你必须理解一些额外的概念:监听器与事件处理程序。监听器决定一个元素是否应该响应某个事件,而事件处理程序则是当事件发生时将要调用的函数。

我们绘制到canvas元素上的形状自身并不具备监测事件的功能。不过,HTML元素具备这一功能,这意味着,可以通过DOM接口捕获用户输入,相对于绘制的对象计算出事件发生的位置,然后根据这一信息作出进一步的决定。本节将介绍如何捕获DOM事件,而随后几章则会介绍如何利用这一点实现动画交互。

2.5.2 监听器与事件处理程序
正如之前介绍的,监听器是监听事件的对象。可以通过调用DOM元素的addEventListener方法指定它作为某个特定事件的监听器。可以传入一个字符串作为该方法的第一个参数,该字符串用于指定所要监听的事件的类型,该方法的第二个参数则是元素接收到事件后将要调用的事件处理程序。函数的语法示意如下:

element.addEventListener(type, handler [, useCapture]);
第三个参数通常是可选的,不过在有些浏览器中例外。为此,在本书中,总会将useCaptured的默认值false传给作为第三个参数的值传入。该参数会影响到事件如何沿着DOM树向上传递,不过该特性与本书的示例无关。有关DOM事件流详见http://www.w3.org/TR/DOM-Level-3-Events/#event-flow中的规范。

一个典型的例子就是监听canvas元素的鼠标单击事件(鼠标事件很快就会讨论到):

canvas.addEventListener('mousedown', function (event) {
 console.log("Mouse pressed on element!");
}, false);

事件类型mousedown作为第一个参数是一个字符串,因此,请确保你仔细检查了所要监听的事件类型,如果拼写错误就意味着你将监听一个不存在的事件。因为addEventListener不会对你指定的事件类型有任何异议,所以这里的bug会很难追踪到,那个时候你就郁闷了。

之前说到监听器会监听事件,其实更确切的说法应该是会通知监听器事件的发生。在内部实现中,触发事件的对象会维护一个列表,其中包含每个监听器对象。如果一个对象能够触发多种类型的事件,如mousedown、mouseup与mousemove,则它会为它可以触发的每一个事件类型维护一个监听器列表。每当其中一个事件发生时,该对象就会遍历对应的列表并通知其中的每个对象什么事件发生了。

描述事件的另一种说法是监听器对象订阅了某个特定的事件,而触发事件的对象则负责把那个事件广播给所有的订阅者。

此外,如果你不想让一个对象再监听某个特定事件,就还可以通过调用其removeEventListener停止监听,或者取消订阅。请注意,该方法的参数与addEventListener 方法完全一致。

element.removeEventListener(type, handler [, useCapture]);
以上代码将监听器从某个特定事件的监听器列表中移除,这样它就无法再接收到之后的事件通知。

让我们看看上述内容是如何应用到示例中的。在之前的框架文件中加入以下代码:

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Event Demo</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <canvas id="canvas" width="400" height="400"></canvas>
 <script>
 window.onload = function () {
**var canvas = document.getElementById('canvas')**;
**canvas.addEventListener('mousedown', function (event) {**
**console.log("mouse down");**
**}, false);**
**canvas.addEventListener('mouseup', function (event) {**
**console.log("mouse up");**
**}, false);**
 };
 </script>
 </body>
</html>

在本例中,首先通过DOM接口使用document.getElementById方法访问canvas元素,然后将其保存在变量canvas中。然后为mouseup和mousedown事件添加了监听器。其中传入内联的回调函数(记住,函数可以作为对象传递),作为事件处理程序,该函数会把消息输出到调试控制台中。作为事件处理程序的回调函数接收一个事件对象作为参数,其中包含了事件相关的信息,如事件的名称以及触发事件的对象。以鼠标事件为例,该参数包含了事件触发时鼠标所在的位置信息,哪个鼠标按键被按下等。而键盘事件则包含了当事件触发时哪个键被按下的信息。

将以上示例代码另存为02-event-demo.html文件。当你在Web浏览器中载入该文件时,你将看到在canvas元素上每次按下或释放鼠标的时候都会在调试控制台中转出一条消息。请确保该示例可以正常运行,以及CSS文件的路径设置正确。这虽然是一个简单的示例,不过它却是检验你的开发环境是否设置正确的一个好测试。

如果你刚开始接触JavaScript,现在能够成功运行该示例并且理解它,那么恭喜你,你已经从初学者升级成为中级学员。

既然你已经对事件处理程序有了大致了解,就可以尝试更好地理解监听器了。一个对象触发一个事件,并将其广播出去,或者通知事件的监听器。它到底做了什么?其实很简单,它只是调用了那个对象上那个函数名与事件处理程序名称一致的函数而已。在之前的示例中,02-event-demo.html文件将canvas添加为一系列鼠标事件的监听器。canvas元素在其内部实现中会为每一个事件维持一个列表,也就是说,针对mousedown事件它会有一个列表,而针对mouseup事件会有另一个列表。

当用户在canvas元素上按鼠标按钮时,canvas回应:“嘿,鼠标按下了!赶紧通知监听器!”。然后它遍历mousedown列表并查看其中内容,当它找到一个被指定为事件处理程序的函数引用后,就会调用监听器上被引用的那个函数。如果还有其他对象也注册为mousedown事件的监听器,那么它们也会出现在那个列表中,并且之前定义的所有事件处理程序都将随之调用到。

同样事情会发生在鼠标释放时,唯一不同的地方在于它此时查看的是mouseup列表。

以上是关于事件和事件处理程序的基础知识。接下来,我们将介绍用于交互的各种事件类型。

2.5.3 鼠标事件
为了捕获一个鼠标事件,必须为一个DOM元素添加一个监听器以处理事件。鼠标事件类型由字符串指定,不同的浏览器所支持的事件类型各不相同。以下是最常见的一些事件:

  • mousedown
  • mouseup
  • click
  • dblclick
  • mousewheel
  • mousemove
  • mouseover
  • mouseout

这些事件类型不言自明。为了了解它们,试着创建并运行以下文件,它会将发生在canvas元素上的每个鼠标事件输出到控制台中。可以在03-mouse-events.html文件中找到完整的示例代码:

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Mouse Events</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <canvas id="canvas" width="400" height="400"></canvas>
 <script>
 window.onload = function () {
  var canvas = document.getElementById('canvas');
  function onMouseEvent (event) {
  console.log(event.type);
  }
  canvas.addEventListener('mousedown', onMouseEvent, false);
  canvas.addEventListener('mouseup', onMouseEvent, false);
  canvas.addEventListener('click', onMouseEvent, false);
  canvas.addEventListener('dblclick', onMouseEvent, false);
  canvas.addEventListener('mousewheel', onMouseEvent, false);
  canvas.addEventListener('mousemove', onMouseEvent, false);
  canvas.addEventListener('mouseover', onMouseEvent, false);
  canvas.addEventListener('mouseout', onMouseEvent, false);
 };
 </script>
 </body>
</html>

注意,我们为每个鼠标事件类型绑定了同一个处理程序,该函数会输出被分派的事件类型。

因为我们只能引用到canvas元素,而无法引用到那些绘制在canvas上的线和形状,所以我们也无法为特定的线或形状添加事件监听器。如果你画了一个矩形并将其作为按钮,canvas是无法获知按钮的边界的。为了监测到按钮单击,需要捕获canvas元素的鼠标事件并经由计算得出鼠标相对于按钮的位置,而这一切都必须由你自己完成。在阅读本书的过程中,你将会看到用于达到这一目的的一些方法。

2.5.4 鼠标位置
每个鼠标事件有两个属性用于确定鼠标的当前位置:pageX与pageY。结合这两个属性以及canvas元素相对文件的偏移量,可以确定鼠标在canvas元素上的相对坐标。遗憾的是,并不是所有的浏览器都支持这两个属性,所以在这些情况下,可能要用到clientX与clientY属性。

考虑到每次需要鼠标位置的时候都要计算偏移量会比较麻烦,同时考虑到示例代码的简洁性,我们决定将这段跨平台的、计算鼠标位置的代码封装到工具函数utils.captureMouse中,并放入untils.js文件中,然后再将其导入文件中:

utils.captureMouse = function (element) {
 var mouse = {x: 0, y: 0};
 element.addEventListener('mousemove', function (event) {
  var x, y;
  if (event.pageX || event.pageY) {
   x = event.pageX;
   y = event.pageY;
  } else {
   x = event.clientX + document.body.scrollLeft +
     document.documentElement.scrollLeft;
   y = event.clientY + document.body.scrollTop +
     document.documentElement.scrollTop;
  }
  x -= element.offsetLeft;
  y -= element.offsetTop;
  mouse.x = x;
  mouse.y = y;
 }, false);
 return mouse;
};

该函数接受一个DOM元素作为参数并为之添加一个mousemove事件的处理程序,最终返回一个包含x与y属性的对象。当鼠标在该元素上移动时,处理程序会根据事件发生的位置(每个浏览器提供不同支持)以及该元素相对于文件的偏移量计算出鼠标在元素上的相对位置,然后将表示位置的两个值赋值给作为函数返回值的对象,最终可以在调用该函数的主脚本中访问它们。鼠标的x、y坐标值是相对于元素的左上角坐标(0,0)而言的。

例如,以下代码,你在脚本开始的地方调用该函数并将canvas元素作为参数值传入:

var canvas = document.getElementById('canvas'),
  mouse = utils.captureMouse(canvas);

在初始化mouse对象之后,每当你需要确认鼠标的当前位置时,可以随时查询它的x与y属性。以下是一个完整的示例,它演示了在本书中如何使用该函数:

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Mouse Position</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <canvas id="canvas" width="400" height="400"></canvas>
**<script src="utils.js"></script>**
 <script>
 window.onload = function () {
  var canvas = document.getElementById('canvas'),
**mouse = utils.captureMouse(canvas);**
  canvas.addEventListener('mousedown', function () {
  console.log("x: " + **mouse.x** + ", y: " + **mouse.y**);
  }, false);
 };
 </script>
 </body>
</html>

在打开调试控制台的情况下运行该文件(04-mouse-position.html)。确保导入了utils.js文件中的utils.captureMouse 函数并把utils.js文件导入该文件。

当用鼠标单击canvas元素时,你会看到一条消息使用mouse.x与mouse.y属性显示了鼠标的当前位置。

2.5.5 触摸事件
由于触摸屏设备越来越流行,因此看看如何借助JavaScript使用它们就成了一个很有价值的想法。触摸事件与鼠标事件相似,不过也有一些显著的不同。一个触摸点可以被想象成一个鼠标光标,不过,鼠标光标会一直停留在屏幕上,而手指却会从设备上按下、移动以及释放,所以某些时刻光标会从屏幕上消失。当查询触摸点的位置时,请务必考虑到这一点。其次,不存在与mouseover等效的触摸事件——要么发生了一次触摸要么没有,不存在手指悬停在触摸屏上的概念。最后,同一时间可能发生多点触摸。某个触摸点的信息会保存在触摸事件的一个数组中,不过在以下示例中仅会用到第一个触摸点。

以下触摸事件将用于与动画的交互:

  • touchstart
  • touchend
  • touchmove

示例05-touch-events.html示范了以上事件的用法,如果你想看到它们发挥作用,请确保你使用了一个支持触摸事件的设备或模拟器。

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Touch Events</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <canvas id="canvas" width="400" height="400"></canvas>
 <script>
 window.onload = function () {
  var canvas = document.getElementById('canvas');
  function onTouchEvent (event) {
  console.log(event.type);
  }
  canvas.addEventListener('touchstart', onTouchEvent, false);
  canvas.addEventListener('touchend', onTouchEvent, false);
  canvas.addEventListener('touchmove', onTouchEvent, false);
 };
 </script>
 </body>
</html>

以上代码做了与鼠标事件的示例(03-mouse-events.html)中完全一样的事情:当在canvas元素上监测到一个触摸事件时,它会把事件类型输出到调试控制台中。

2.5.6 触摸位置
类似utils.captureMouse函数,可以使用utils.captureTouch函数确定第一个手指在元素上触摸的位置。两个函数的实现类似,不过由于可能出现没有手指触摸到屏幕的情况,因此为返回的对象添加了一个isPressed属性。同时,如果没有手指触摸到屏幕,x与y属性就会设置为null。将以下函数添加到utils.js文件中:

utils.captureTouch = function (element) {
 var touch = {x: null, y: null, isPressed: false};
 element.addEventListener('touchstart', function (event) {
  touch.isPressed = true;
 }, false);
 element.addEventListener('touchend', function (event) {
  touch.isPressed = false;
  touch.x = null;
  touch.y = null;
 }, false);
 element.addEventListener('touchmove', function (event) {
  var x, y,
   touch_event = event.touches[0]; //first touch
  if (touch_event.pageX || touch_event.pageY) {
   x = touch_event.pageX;
   y = touch_event.pageY;
  } else {
   x = touch_event.clientX + document.body.scrollLeft +
    document.documentElement.scrollLeft;
   y = touch_event.clientY + document.body.scrollTop +
    document.documentElement.scrollTop;
  }
  x -= offsetLeft;
  y -= offsetTop;
  touch.x = x;
  touch.y = y;
 }, false);
 return touch;
};

以上函数定义与获取鼠标位置的那个版本类似,不过加入了一些额外的事件监听器。它接收一个HTML元素作为参数同时返回一个包含x、y与isPressed属性的对象。Touchmove事件处理程序负责追踪第一个触摸点与元素的相对坐标,其中一些跨浏览器的代码用来计算偏移量。还分别为touchstart与touchend事件添加了事件处理程序,其中会分别将isPressed属性设置为ture与false,如果不存在有效的触摸点,touchend事件处理程序还会将x、y属性设置为null。

可以像之前一样初始化一个touch对象:

var canvas = document.getElementById('canvas'),
  touch = utils.captureTouch(canvas);

在访问x、y属性之前务必确保此时有触摸点按下,否则,它们的值将为null,并很可能把动画计算弄错。因此,在查询触摸位置前,务必检查是否有触摸点按下:

if (touch.isPressed) {
  console.log("x: " + touch.x + ", y: " + touch.y);
}

由于我们更关注使得示例代码能够工作的数学原理,因此触摸事件不会在本书中大量使用。不过,如果你想在一个触摸设备上体验动画效果,以上内容应该足以让你知道如何使用它。

2.5.7 键盘事件
键盘事件类型与所有事件类型一样都是由字符串定义的,不过仅包含以下两个:

 keydown

 keyup

可以在支持字符输入的HTML元素上监听键盘事件,例如,一个文本区域元素,监听的方法与你监听canvas元素上的鼠标事件一致。不过,在捕获键盘事件之前,首先需要将屏幕的焦点设置到该元素上。假设变量textarea中保存了一个HTML textarea元素,可以通过如下方式设置它的焦点并捕获keydown事件:

textarea.focus();
textarea.addEventListener('keydown', function (event) {
 console.log(event.type);
}, false);

不过,在大多数情况下,更加简单的做法是直接在网页上监听键盘事件而不管谁获得了焦点。为了做到这一点,可以直接将一个键盘事件监听器绑定到全局的window对象上。以下示例会监测何时用户按下与释放某个键,而不管哪个元素持有焦点(示例06-keyboard-events.html)。

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Keyboard Events</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <script>
 window.onload = function () {
  function onKeyboardEvent (event) {
  console.log(event.type);
  }
  window.addEventListener('keydown', onKeyboardEvent, false);
  window.addEventListener('keyup', onKeyboardEvent, false);
 };
 </script>
 </body>
</html>

2.5.8 键盘码
通常在事件处理程序中,你想知道哪个键按下。之前在介绍事件的时候,我们看到事件处理程序中传入了一个事件对象,其中包含事件发生的相关信息。在一个键盘事件中,可以通过事件对象的keyCode属性获知哪个键按下。

KeyCode属性包含一个代表按下的物理键的数字值。如果用户按下a键,那么无论是否有其他键按下,keyCode都会包含数字65。如果用户先按下Shift键再同时按下a,我们将获得两个键盘事件,一个是Shift键的事件(键盘码16),另一个为a键的事件(键盘码65)。

在下面这个示例中,测试按下的键盘码是否是方向键的键盘码,方向键的键盘码包含37、38、39与40这些值。如果按下的键与其中任何一个值匹配,会向控制台输出一条消息,显示按下的方向;否则,输出那个未知的键盘码。以下是HTML文件07-key-codes.html的代码:

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Key Codes</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
 <script>
 window.onload = function () {
  function onKeyboardEvent (event) {
  switch (event.keyCode) {
  case 38:
   console.log("up!");
   break;
  case 40:
   console.log("down!");
  break;
  case 37:
  console.log("left!");
  break;
  case 39:
  console.log("right!");
  break;
  default:
  console.log(event.keyCode);
  }
 }
 window.addEventListener('keydown', onKeyboardEvent, false);
 };
 </script>
 </body>
</html>

通常你不会记得你想捕获的键的数字值。为了方便查询这一信息,创建了一个keycode.js文件作为参考速记表,甚至可以把它导入文件中,这样就可以利用键的名称查询键盘码而不用直接使用数字,并且使代码更加易懂。该脚本将创建一个全局对象,其中包含一系列以键的名称作为属性名的属性,把属性值映射到键所对应的键盘码,这些属性涵盖了键盘上几乎所有的键。
让我们用使用键名称而不是键盘码改写之前的示例。将keycode.js文件放在该示例所在的目录下,并导入08-key-names.html文件中:

<!doctype html>
<html>
 <head>
 <meta charset="utf-8">
 <title>Key Names</title>
 <link rel="stylesheet" href="style.css">
 </head>
 <body>
**<script src="keycode.js"></script>**
 <script>
 window.onload = function () {
 function onKeyboardEvent (event) {
  switch (event.keyCode) {
**case keycode.UP:**
   console.log("up!");
   break;
**case keycode.DOWN:**
   console.log("down!");
   break;
**case keycode.LEFT:**
   console.log("left!");
   break;
**case keycode.RIGHT:**
   console.log("right!");
   break;
  default:
   console.log(event.keyCode);
  }
  }
  window.addEventListener('keydown', onKeyboardEvent, false);
 };
 </script>
 </body>
</html>

以上示例会像之前的示例07-key-codes.html一样输出同样的消息,不过由于我们现在使用键名称而不是键盘码,因此switch语句变得更加容易理解了。而且除了让代码更加清晰之外,这种做法还可以避免不小心输错数字。

不过本书中的示例并不打算使用keycode.js,因为书中的示例相对简单,而且我希望读者能更加关注于示例的中心思想而不是这些细枝末节。但是,当我们编写一个更加复杂的程序时,我们应该考虑使用该文件,因为它确实有助于提升代码的可读性。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
HTML基础9--兼容性问题
计算一定要精确 不要让内容的宽高超出我们设置的宽高在IE6下,内容会撑开设置好的宽高 在IE6元素浮动,如果宽度需要内容撑开,就给里边的块元素都加浮动 在IE6,7下元素要通过浮动并在同一行,就给这行元素都加浮动 注意标签嵌套规范 IE6...
566 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9490 0
HTML基础5--浮动练习
访问各大网站,实现其中的效果。
600 0
HTML基础6--定位:绝对定位、相对定位,其他定位和定位问题
HTML中如何设置一个元素所在位置? relative相对定位/定位偏移量 absolute绝对定位/定位层级
561 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13176 0
HTML基础-样式位置、背景、边框、内外边距、盒模型和结构、文本、常见样式
HTML是什么? W3C? 一个网页的组成? 样式表出现的位置? 设置背景?复合属性 边框? 内边距?注意增加内边距对盒模型的影响 外边距? 盒模型? 文本设置?
810 0
HTML基础4--浮动
浮动的原理?float有哪几个方向的浮动? 什么是文档流? 使用浮动有什么缺点? 如何清除浮动? 浮动的兼容性问题?IE6下双边距BUG?
630 0
HTML基础3--内嵌、块元素
什么是CSS reset? 什么是内嵌元素? 什么是块元素? 块和内嵌的转换? 面试题??? 前端规范(部分)? 1、所有书写均在英文半角状态下的小写; 2、id,class必须以字母开头; 3、所有标签必须闭合; 4、html标签用tab键缩进;...
557 0
+关注
异步社区
异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
12049
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载