EventTarget接口、事件模型

需要理解的部分: 1、addEventListene三个参数的含义 2、DOM的this指向 3、事件的传播 4、事件的代理

addEventListener()

  1. target.addEventListener(type, listener[, useCapture]);
  • type:事件名称,大小写敏感。
  • listener:监听函数。事件发生时,会调用该监听函数。
  • useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。

    事件的传播

    事件 - 图1
    一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。

  • 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。

  • 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
  • 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
  1. var phases = {
  2. 1: 'capture',
  3. 2: 'target',
  4. 3: 'bubble'
  5. };
  6. var div = document.querySelector('div');
  7. var p = document.querySelector('p');
  8. div.addEventListener('click', callback, true);
  9. p.addEventListener('click', callback, true);
  10. div.addEventListener('click', callback, false);
  11. p.addEventListener('click', callback, false);
  12. function callback(event) {
  13. var tag = event.currentTarget.tagName;
  14. var phase = phases[event.eventPhase];
  15. console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");
  16. }
  17. // 点击以后的结果
  18. // Tag: 'DIV'. EventPhase: 'capture'
  19. // Tag: 'P'. EventPhase: 'target'
  20. // Tag: 'P'. EventPhase: 'target'
  21. // Tag: 'DIV'. EventPhase: 'bubble'

上面代码表示,click事件被触发了四次:

节点的捕获阶段和冒泡阶段各1次,

节点的目标阶段触发了2次。

  1. 捕获阶段:事件从

    传播时,触发

    的click事件;
  2. 目标阶段:事件从
    到达

    时,触发

    的click事件;

  3. 冒泡阶段:事件从

    传回

    时,再次触发
    的click事件。

其中,

节点有两个监听函数(addEventListener方法第三个参数的不同,会导致绑定两个监听函数),因此它们都会因为click事件触发一次。所以,

会在target阶段有两次输出。
注意,浏览器总是假定click事件的目标节点,就是点击位置嵌套最深的那个节点(本例是

节点里面的

节点)。所以,

节点的捕获阶段和冒泡阶段,都会显示为target阶段。
事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.body)。也就是说,上例的事件传播顺序,在捕获阶段依次为window、document、html、body、div、p,在冒泡阶段依次为p、div、body、html、document、window。

事件代理

在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理

  1. var ul = document.querySelector('ul');
  2. ul.addEventListener('click', function (event) {
  3. if (event.target.tagName.toLowerCase() === 'li') {
  4. // some code
  5. }
  6. });

event.stopPropagation();阻止冒泡的方法

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. <style>
  9. #container {
  10. width: 100px;
  11. height: 100px;
  12. border: 1px solid red;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. <div id="container">
  18. <button id="btn" attr="111">点击</button>
  19. </div>
  20. <script>
  21. var button = document.getElementById('btn');
  22. button.addEventListener('click', function (event) {
  23. event.stopPropagation();
  24. alert('Hello world');
  25. }, false);
  26. var div = document.getElementById('container');
  27. // 这里重点理解捕获阶段和冒泡阶段的区别
  28. div.addEventListener('click', hellos, false);
  29. function hellos() {
  30. alert('Hello world');
  31. }
  32. </script>
  33. </body>
  34. </html>

Event对象

重点掌握实例方法:

  1. Event.preventDefault()
  2. Event.stopPropagation()
  3. Event.stopImmediatePropagation()

Event.preventDefault()

Event.preventDefault方法取消浏览器对当前事件的默认行为。该方法生效的前提是,事件对象的cancelable属性为true,如果为false,调用该方法没有任何效果。
该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用stopPropagation()或stopImmediatePropagation()方法。

  1. // <input type="checkbox" id="my-checkbox" />
  2. var cb = document.getElementById('my-checkbox');
  3. cb.addEventListener(
  4. 'click',
  5. function (e){ e.preventDefault(); },
  6. false
  7. );

Event.stopPropagation()

stopPropagation方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。

Event.stopImmediatePropagation()

Event.stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()更彻底。

鼠标事件

MouseEvent.clientX,MouseEvent.clientY

MouseEvent.clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标(单位像素),MouseEvent.clientY属性返回垂直坐标。这两个属性都是只读属性。

MouseEvent.movementX,MouseEvent.movementY

MouseEvent.movementX属性返回当前位置与上一个mousemove事件之间的水平距离(单位像素)。数值上,它等于下面的计算公式。

  1. currentEvent.movementX = currentEvent.screenX - previousEvent.screenX

MouseEvent.movementY属性返回当前位置与上一个mousemove事件之间的垂直距离(单位像素)。数值上,它等于下面的计算公式。

  1. currentEvent.movementY = currentEvent.screenY - previousEvent.screenY

MouseEvent.screenX,MouseEvent.screenY

MouseEvent.screenX属性返回鼠标位置相对于屏幕左上角的水平坐标(单位像素)
MouseEvent.screenY属性返回垂直坐标。这两个属性都是只读属性。

  1. // HTML 代码如下
  2. // <body onmousedown="showCoords(event)">
  3. function showCoords(evt) {
  4. console.log(
  5. 'screenX value: ' + evt.screenX + '\n',
  6. 'screenY value: ' + evt.screenY + '\n'
  7. );
  8. }

MouseEvent.offsetX,MouseEvent.offsetY

MouseEvent.offsetX属性返回鼠标位置与目标节点左侧的padding边缘的水平距离(单位像素),MouseEvent.offsetY属性返回与目标节点上方的padding边缘的垂直距离。这两个属性都是只读属性。

  1. /* HTML 代码如下
  2. <style>
  3. p {
  4. width: 100px;
  5. height: 100px;
  6. padding: 100px;
  7. }
  8. </style>
  9. <p>Hello</p>
  10. */
  11. var p = document.querySelector('p');
  12. p.addEventListener(
  13. 'click',
  14. function (e) {
  15. console.log(e.offsetX);
  16. console.log(e.offsetY);
  17. },
  18. false
  19. );

MouseEvent.pageX,MouseEvent.pageY

MouseEvent.pageX属性返回鼠标位置与文档左侧边缘的距离(单位像素),MouseEvent.pageY属性返回与文档上侧边缘的距离(单位像素)。它们的返回值都包括文档不可见的部分。这两个属性都是只读。

  1. /* HTML 代码如下
  2. <style>
  3. body {
  4. height: 2000px;
  5. }
  6. </style>
  7. */
  8. document.body.addEventListener(
  9. 'click',
  10. function (e) {
  11. console.log(e.pageX);
  12. console.log(e.pageY);
  13. },
  14. false
  15. );

拖拉事件

1、拖拽的几个事件

实现元素块的任意拖拉拽

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div class="move-container">
  11. <div class="move" style="position:absolute; width:100px; height:100px; background:gray">
  12. </div>
  13. </div>
  14. <script>
  15. var moveElem = document.querySelector('.move'); //待拖拽元素
  16. var dragging; //是否激活拖拽状态
  17. var tLeft, tTop; //鼠标按下时相对于选中元素的位移
  18. //监听鼠标按下事件
  19. document.addEventListener('mousedown', function (e) {
  20. if (e.target == moveElem) {
  21. dragging = true; //激活拖拽状态
  22. var moveElemRect = moveElem.getBoundingClientRect();
  23. tLeft = e.clientX - moveElemRect.left; //鼠标按下时和选中元素的坐标偏移:x坐标
  24. tTop = e.clientY - moveElemRect.top; //鼠标按下时和选中元素的坐标偏移:y坐标
  25. }
  26. });
  27. //监听鼠标放开事件
  28. document.addEventListener('mouseup', function (e) {
  29. dragging = false;
  30. });
  31. //监听鼠标移动事件
  32. document.addEventListener('mousemove', function (e) {
  33. if (dragging) {
  34. var moveX = e.clientX - tLeft,
  35. moveY = e.clientY - tTop;
  36. moveElem.style.left = moveX + 'px';
  37. moveElem.style.top = moveY + 'px';
  38. }
  39. });
  40. </script>
  41. </body>
  42. </html>

其他事件

1、资源事件 2、session 历史事件 3、窗口事件【scroll】、resize 事件 4、剪贴板事件

资源事件

beforeunload事件在窗口、文档、各种资源将要卸载前触发。它可以用来防止用户不小心卸载资源。
大多数浏览器在对话框中不显示指定文本,只显示默认文本。

  1. window.addEventListener('beforeunload', function (e) {
  2. var confirmationMessage = '确认关闭窗口?';
  3. e.returnValue = confirmationMessage;
  4. return confirmationMessage;
  5. });

popstate 事件

popstate事件在浏览器的history对象的当前记录发生显式切换时触发。

  1. window.onpopstate = function (event) {
  2. console.log('state: ' + event.state);
  3. };
  4. history.pushState({page: 1}, 'title 1', '?page=1');
  5. history.pushState({page: 2}, 'title 2', '?page=2');
  6. history.replaceState({page: 3}, 'title 3', '?page=3');
  7. history.back(); // state: {"page":1}
  8. history.back(); // state: null
  9. history.go(2); // state: {"page":3}

hashchange 事件

hashchange事件在 URL 的 hash 部分(即#号后面的部分,包括#号)发生变化时触发。该事件一般在window对象上监听。

  1. // URL 是 http://www.example.com/
  2. window.addEventListener('hashchange', myFunction);
  3. function myFunction(e) {
  4. console.log(e.oldURL);
  5. console.log(e.newURL);
  6. }
  7. location.hash = 'part2';
  8. // http://www.example.com/
  9. // http://www.example.com/#part2

scroll 事件

这里需要注意解决大量触发的事件。也就是常说的节流

  1. (function () {
  2. var throttle = function (type, name, obj) {
  3. var obj = obj || window;
  4. var running = false;
  5. var func = function () {
  6. if (running) { return; }
  7. running = true;
  8. requestAnimationFrame(function() {
  9. obj.dispatchEvent(new CustomEvent(name));
  10. running = false;
  11. });
  12. };
  13. obj.addEventListener(type, func);
  14. };
  15. // 将 scroll 事件转为 optimizedScroll 事件
  16. throttle('scroll', 'optimizedScroll');
  17. })();
  18. window.addEventListener('optimizedScroll', function() {
  19. console.log('Resource conscious scroll callback!');
  20. });

resize 事件

resize事件在改变浏览器窗口大小时触发,主要发生在window对象上面。
可以动态计算iframe的大小视口

  1. var resizeMethod = function () {
  2. if (document.body.clientWidth < 768) {
  3. console.log('移动设备的视口');
  4. }
  5. };
  6. window.addEventListener('resize', resizeMethod, true);

剪贴板事件

需求:选中文字并把文字复制到粘贴板中

  1. <input type="text" value="被选中的对象" id="input"/>
  2. <script>
  3. const inputElement = document.querySelector('#input');
  4. inputElement.select();
  5. inputElement.addEventListener('click',function () {
  6. document.execCommand('copy');
  7. })
  8. </script>