一、事件

事件:浏览器赋予元素的默认行为,可以理解为事件是天生具备的。不论是否为其绑定方法,当某些行为触发的时候,相关的事件都会被触发执行。
事件绑定:给元素默认的事件行为绑定方法,这样在行为触发的时候才会执行绑定的方法。
document.body.onclick=function (){}; 大部分人:给body绑定一个点击事件 标准:给body的点击事件行为绑定方法

事件行为还有很多,这里我们暂时列举这些;更多的内容可以参考 MDN,事件参考; 或者可以查看元素的属性(属性中onxxx就是元素拥有的事件行为)

1.鼠标事件

  • 元素.onclick=function(){} 单击(移动端:300ms内没触发第二次,所以click在移动端有300ms延迟);点击(PC端)
  • 元素.oncontextmenu=function(){}右键点击
  • 元素.ondblclick=function(){} 双击,大约300ms内连续点击两次
  • 元素.onmouseenter=function(){}鼠标移入,进入子节点不会触发这个事件
  • 元素.onmouseleave=function(){}鼠标离开,进入子节点不会触发这个事件
  • 元素.onmouseover=function(){}鼠标滑入,进入子节点会触发这个事件
  • 元素.onmouseout=function(){}鼠标滑出,进入子节点会触发这个事件
  • 元素.onmousemove=function(){}鼠标滑动,只要鼠标动就会触发
  • 元素.onmousedown=function(){}鼠标按下 鼠标的左右和滚轮按下就会触发
  • 元素.onmouseup=function(){}鼠标抬起
  • 元素.onwheel=function(){}滚轮滚动

    思考:如何让按钮单击和双击做不同的事情

    1. let btn=document.querySelector('.two');
    2. btn.onclick=function(){
    3. // 鼠标点击第一次的时候,先不着急执行单击函数 等一会 看是否点击了第二下
    4. // 如果点击了第二下,就自动触发了双击,这样就不执行对应的代码了
    5. clearTimeout(this.timer);
    6. this.dbl=false;
    7. this.timer=setTimeout(() => {
    8. if(this.dbl)return;
    9. console.log('单击'); //单击业务
    10. }, 300);
    11. };
    12. btn.ondblclick=function(){
    13. this.dbl=true;
    14. console.log('双击');
    15. };

    思考:mouseover和mouseenter的区别

    mouseover和mouseout是从视觉上看进入离开
    mouseover和mouseout是忽略元素之间的层级关系,只看鼠标在哪个元素显示的范围上,存在事件冒泡
    阻止事件冒泡的话:e.stopPropagation();
    image.png

mouseenter和mouseleave是从层级上来进入和离开
mouseenter和mouseleave会受到元素之间的层级关系,默认阻止了事件冒泡机制

image.png

2.键盘事件

  • 元素.onkeydown=function(){}键盘按下,所有键都行
  • 元素.onkeyup=function(){}键盘抬起
  • 元素.onkeypress=function(){} 长按(有些键获取不到,如:Shift/Fn/CapsLock等)
  • 上面元素有限制:input textarea window document document.body document.documentElement

    3.表单事件

  • input.onfocus=function(){}获取光标时触发

  • input.onblur=function(){}失去焦点时触发
  • input.onchange=function(){}内容改变并且失焦
  • input.oninput=function(){}只要内容改变就会触发
  • submit表单提交(前提:表单元素都包含在form中,并且点击的按钮是 submit)
  • reset表单重置
  • select下拉框内容选中

    this.value代表输入的内容

4.系统常用事件

  • window.onload=function(){}页面全部加载完
  • window.onresize=function(){}可视窗口发生改变时
  • document.body.onscroll=function(){}滚动条监听
  • window.onscroll=function(){}

    5.移动端常用事件

    单手指事件 Touch Event

  • 元素.ontouchstart=function(){} 触碰到元素触发【手指碰到屏幕】

  • 元素.ontouchend=function(){}离开元素触发【手指离开屏幕】
  • 元素.ontouchmove=function(){} 手指移动
  • 元素.onclick=function(){} 单击大约300ms延迟 【在移动端一般不用】
  • 元素.ontouchcancel:操作取消(一般应用于非正常状态下操作结束)

    多手指事件模型 Gesture

  • ongesturestart:手指碰到屏幕(手指按下)

  • ongesturechange / ongestureupdate:手指在屏幕上移动
  • ongestureend:手指离开屏幕(手指松开)
  • ongesturecancel:操作取消(一般应用于非正常状态下操作结束)

    6.css3动画事件(了解)

  • transitionend transition动画结束

  • transitionstart transition动画开始
  • transitionrun transition动画运行中

    7.音视频事件

    使用范围:音频、视频

  • canplay:可以播放(资源没有加载完,播放中可能会卡顿)

  • canplaythrough:可以播放(资源已经加载完,播放中不会卡顿)
  • play:开始播放
  • playing:播放中
  • pause:暂停播放

    事件行为还有很多,这里我们暂时列举这些;更多的内容可以参考 MDN,事件参考; 或者可以查看元素的属性(属性中onxxx就是元素拥有的事件行为)

二、事件对象

1、定义:

  • 给元素的事件行为绑定方法,当事件行为触发方法会被执行,不仅被执行,而且还会把当前操作的相关信息传递给这个函数 =>传递过来相关信息就叫做事件对象

    事件对象是由事件当前本身产生的,和执行什么函数没有关系

2、原理:

事件对象和函数以及给谁绑定的事件没啥必然关系,它存储的是当前本次操作的相关信息,操作一次只能有一份信息,所以在哪个方法中获取的信息都是一样的;第二次操作,存储的信息会把上一次操作存储的信息替换掉…;

  • 每一次事件触发,浏览器都会这样处理一下:
    • 1.捕获到当前操作的行为(把操作信息获取到),通过创建MouseEvent等类的实例,得到事件对象EV
    • 2.通知所有绑定的方法(符合执行条件的)开始执行,并且把EV当做实参传递给每个方法,所以在每个方法中得到的事件对象其实是一个
    • 3.后面再重新触发这个事件行为,会重新获取本次操作的信息,用新的信息替换老的信息,然后继续之前的步骤…

      3、事件对象类型

      -1)鼠标事件对象

      如果是鼠标操作,获取的是MouseEvent类的实例(这个实例就是 =>鼠标事件对象)

  1. var box = document.getElementsByClassName('box')[0];
  2. box.onclick = function (e) {
  3. //e 我们一般称为事件对象 是浏览器默认传给这个函数的
  4. //里面存储了事件的信息
  5. e = e || window.event; //为了兼容低版本IE浏览器
  6. target = e.target || e.srcElement;//兼容低版本IE浏览器
  7. console.log(e);
  8. }

image.png
image.png

  • 原型链:
    • 鼠标事件对象 -> MouseEvent.prototype -> UIEvent.prototype -> Event.prototype -> Object.prototype
  • 常用属性:
    • clientX/clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴坐标
    • pageX/pageY:触发点距离当前页面左上角的X/Y轴坐标

DOM事件 - 图7

-2)键盘事件对象

如果是键盘操作,获取的是KeyboardEvent类的实例 =>键盘事件对象

image.png

  • 常用属性:
    • code & key:存储的都是按键,code更细致
    • keyCode & which:存储的是键盘按键对应的码值
  • 键盘常用码值:
    • 方向键:37 38 39 40 =>左上右下
    • 空格SPACE:32
    • 回车ENTER:13
    • 回退BACK:8
    • 删除DEL:46
    • SHIFT:16
    • CTRL:17
    • ALT:18

window键盘码值:
DOM事件 - 图9

-3)手指事件对象

changedTouches / targetTouches / touches都是记录手指信息的,平时用的多的是changedTouches
手指按下、移动、离开屏幕 changedTouches都存储了对应的手指信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕 中的信息;而 touches在手指离开屏幕后,就没有任何的信息了;=>获取的结果都是一个 TouchList集合,记录每一根手指的信息;
image.png

4、事件对象event的属性

除了上面,只有鼠标和键盘中有的属性外,还有一些公共的所有事件对象都有的属性

  • type:触发事件的类型
  • target:事件源(操作的是哪个元素,哪个元素就是事件源)
    • 在不兼容的浏览器中可以使用srcElement获取,也代表的是事件源
  • preventDefault():用来阻止默认行为的方法
    • 不兼容的浏览器中用ev.returnValue=false也可以阻止默认行为
  • stopPropagation():阻止冒泡传播
    • 不兼容的浏览器中用ev.cancelBubble=true也可以阻止冒泡传播

      需求:当用户输入完成,敲回车键的时候 alert用户输入的内容

      1. let ipt=document.querySelector('#inp');
      2. ipt.onkeydown=function(e){
      3. if(e.keyCode==13){
      4. alert(e.target.value);
      5. };
      6. };

三、阻止默认事件

a标签、onkeydown、ontouchmove 、右键菜单 有默认事件

  • 一般优先执行绑定的函数,再去执行默认行为
  • onscroll touchmove 先执行默认
  • e.preventDefault();//阻止默认事件
  • e.returnValue=false;
  • return false

    1. //页面标签<a href="http://www.baidu.com" target="_blank">点击百度</a>
    2. //=> A标签的默认行为
    3. // +页面跳转
    4. // +锚点定位(定位到当前页面指定的ID的盒子里,URL地址会加入HASH值)
    5. // 第一种直接在 <a href="javascript:;" target="_blank">点击百度</a>
    6. let a=document.querySelector('#box a');
    7. //点击A标签,先触发其点击事件行为 然后才是默认跳转
    8. a.onclick=function(e){
    9. // 阻止默认事件
    10. //return false
    11. e.preventDefault();
    12. //e.returnValue=false;//兼容IE
    13. }

    需求:点击右键不出现系统菜单,出现自定义菜单

    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. * {
    10. margin: 0;
    11. padding: 0;
    12. }
    13. #box {
    14. position: relative;
    15. left: 0px;
    16. top: 0px;
    17. width: 100px;
    18. height: 100px;
    19. background: lightcoral;
    20. transition: all ease;
    21. }
    22. .menu {
    23. position: fixed;
    24. display: none;
    25. width: 50px;
    26. height: 200px;
    27. background-color: lightcoral;
    28. }
    29. ul {
    30. list-style: none;
    31. text-align: center;
    32. }
    33. </style>
    34. </head>
    35. <body>
    36. <div id="box">
    37. <div class="menu">
    38. <ul>
    39. <li>确定</li>
    40. <li>打印</li>
    41. </ul>
    42. </div>
    43. </div>
    44. <script>
    45. let menuDiv = document.querySelector('#box .menu');
    46. console.log(menuDiv);
    47. window.oncontextmenu = function (e) {
    48. e.preventDefault();
    49. let left = e.clientX,
    50. top = e.clientY;
    51. // 拿到鼠标的点击位置 把点击的位置信息给menuDiv
    52. menuDiv.style.left = left + 'px';
    53. menuDiv.style.top = top + 'px';
    54. menuDiv.style.display = 'block';
    55. };
    56. window.onclick = function () {
    57. menuDiv.style.display = 'none';
    58. };
    59. </script>
    60. </body>
    61. </html>

    四、事件传播机制

    DOM事件 - 图11

    1事件传播机制 先捕获 再处理 再冒泡

    Event.prototype:Event 原型上记录了冒泡传播的顺序 啥都不做是NONE 0
    DOM事件 - 图12

  • 1、捕获阶段:=>CAPTURING_PHASE:1

    • 会从最外层元素一层一层往里找到该元素,这个过程叫捕获
      • 采集冒泡阶段的传播路径 ev.path[传播路径]
      • 在IE低版本和欧朋低版本浏览器里没有捕获阶段
      • element.addEventListener(event, function, true);//第三个参数 就可以实现事件捕获
  • 2、目标阶段:=>AT_TARGET:2
    • 【目标处理阶段】把事件源的相关事件行为触发 如果为其绑定过方法,方法会执行
  • 3、冒泡阶段:=>BUBBLING_PHASE:3
    • 从里到外,把当前事件源所有的祖先元素的相关事件行为都依次触发
    • element.addEventListener(event, function, false);//第三个参数为false或者为空的时候,代表在冒泡阶段绑定

DOM事件 - 图13

image.png

2.阻止冒泡传播

e.stopPropagation();

  • 不兼容的浏览器中用ev.cancelBubble=true也可以阻止默认行为
<!-- CSS 代码 -->
<style>
      #box2{
          width: 500px;
          height: 500px;
          background:lightskyblue;
      }
      #outer,#inner{
        margin: auto;
      }
      #outer{
        width: 300px;
        height: 300px;
        background-color: lightcoral;
      }
      #inner{
          width: 100px;
          height: 100px;
          background-color: yellowgreen;
      }
</style>

<!-- HTML 代码 -->
<div id="box2">
        box2
        <div id="outer">
            outer
            <div id="inner">
                inner
            </div>
        </div>
</div>
<!-- JS 代码 -->
let box = document.querySelector('#box2'),
    outer = document.querySelector('#outer'),
    inner = document.querySelector('#inner');
box.onclick = function () {
    console.log('box');
};
outer.onclick = function () {
    console.log('outer');   
};
inner.onclick = function (e) {
    console.log('inner');
    e.stopPropagation(); //阻止冒泡
};

五、事件委托[事件代理]

  • 通过点击子级元素来触发父级元素的事件【因为事件的冒泡机制】
  • 利用事件源e.target||e.srcElment来获取点击的元素
  • 本来是要绑定给每一个子元素事件,我们可以绑定到父元素

    • 通过事件的冒泡结合 e.target 实现了要的效果,避免了给每个一个元素绑定点击事件
    • 每个元素绑定事件,多了话 利用给父元素绑定事件 比每个元素绑定事件性能高

      1、应用场景一

      如果一个容器中很多元素都要在触发某一事件的时候做一些事情的时候,
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      </head>
      <body>
      <input type="number" value="20" disabled>
      <div id="box">
         <button data-type="+">+</button>
         <button data-type="-">-</button>
         <button data-type="+">+</button>
         <button data-type="-">-</button>
         <button data-type="+">+</button>
      </div>
      <script>
         let box=document.querySelector('#box'),
             input=document.querySelector('input');
         //点击父盒子绑定事件 
         box.onclick=function(e){
             //e.target就是点击的元素
             //e.target.tagName 鼠标点击的dom的标签
             if(e.target.tagName==='BUTTON'){
                 let type=e.target.getAttribute('data-type');
                 if(type==='+'){
                     console.log(input.value);
                     input.value++;           
                 }else{
                     input.value--;
                 }
             };
         };
      </script>
      </body>
      </html>
      

      六、DOM0 和 DOM2 事件绑定

      1、DOM0 事件绑定

  • 语法:元素.on事件类型[onclick]=function(){}

  • 原理:基于给元素的私有属性赋值,当条件达到触发私有属性方法
  • 只能绑定一个方法,如果多个绑定,只有最后一个绑定上
  • 都是冒泡阶段触发

image.png

box.onclick = function () {
    console.log(1);
}
box.onclick = function () {
    console.log(2);
}

只输出后面的:
image.png

  • 移除

    box.onclick = function () {
      console.log(2);
      //=>移除事件绑定:DOM0直接赋值为null即可
      box.onclick = null;
    }
    

    2、DOM2 事件绑定

  • 语法:

    • 元素.addEventListener(事件类型,fn,true/false)
      • true/false 可以省略,默认是false冒泡阶段; true代表的是捕获阶段
      • IE6~8中:元素.attachEvent(‘on事件行为’,function(){})
  • 原理:
    • 基于原型链查找机制,找到EventTarget.prototype上的方法并且执行,此方法执行,会把给当前元素某个事件行为绑定的所有方法,存放到浏览器默认的事件池中(绑定几个方法,会向事件池存储几个);
    • 当事件行为触发,会把事件池中存储的对应方法,依次按照顺序执行 “给当前元素某一个事件行为绑定多个不同方法”

      事件池特点:

      • 基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
function fn(){
        console.log(1);
    };
function fn2(){
        console.log(2)
    };
box.addEventListener('click',fn);
box.addEventListener('click',fn2);

两个都能输出:
image.png

  • 绑定注意点:DOM2事件绑定的时候,我们一般都采用实名函数

    • 目的:这样可以基于实名函数去移除事件绑定
    • 语法:box.addEventListener(‘click’, fn, false);
      function fn1(){ console.log(1); }
      function fn2(){ console.log(2); }
      function fn3(){ console.log(3); }
      box.addEventListener('click', fn2, false); 
      box.addEventListener('click', fn3, false); 
      box.addEventListener('click', fn1, false); 
      //输出结果 2 3 1  先存先执行
      //=>基于addEventListener向事件池增加方法,存在去重的机制 “同一个元素,同一个事件类型,在事件池中只能存储一遍这个方法,不能重复存储”
      box.addEventListener('click', fn1, false); // 所以此步跳过,不再存储
      box.addEventListener('mouseover', fn1, false); //增加
      
      DOM事件 - 图18
  • 移除绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)

    • 语法:box.removeEventListener(‘click’, fn, false)
      function fn() {
      console.log(1);
      //=>移除事件绑定:从事件池中移除,所以需要指定好事件类型、方法等信息(要和绑定的时候一样才可以移除)
      box.removeEventListener('click', fn, false);
      }
      box.addEventListener('click', fn, false);
      

      3、特别注意的几点

  • -1)DOM0和DOM2可以混在一起用:执行的顺序以绑定的顺序为主

    box.addEventListener('click', function () {
      console.log('哔咔哔咔~~');
    });
    box.onclick = function () {
      console.log('哇咔咔~~');
    }
    box.addEventListener('click', function () {
      console.log('call~~');
    });
    

    DOM事件 - 图19

  • -2)DOM0 比 DOM2 快

  • -3)DOM0中能做事件绑定的事件行为,DOM2都支持;DOM2里面一些事件,DOM0不一定能处理绑定,例如:transitionend、DOMContentLoaded…

    box.style.transition = 'opacity 1s';
    box.ontransitionend = function () {
      console.log('哇咔咔~~');
    }
    box.addEventListener('transitionend', function () {
      console.log('哇咔咔~~');
    }); 
    window.addEventListener('load', function () {
      //=>所有资源都加载完成触发
      console.log('LOAD');
    });
    window.addEventListener('DOMContentLoaded', function () {
      //=>只要DOM结构加载完就会触发
      console.log('DOMContentLoaded');
    }); 
    //=>$(document).ready(function(){})
    $(function () {
      //=>JQ中的这个处理(DOM结构加载完触发)采用的就是DOMContentLoaded事件,并且依托        DOM2事件绑定来处理,所以同一个页面中,此操作可以被使用多次
    });
    /* JQ中的事件绑定采用的都是DOM2事件绑定,例如:on/off/one */
    

    4、window.onload 和 $(document).ready()的区别

  • -1)window.onload

    • 必须等待所有资源都加载完成才会被触发执行,比ready晚
    • 采用DOM0事件绑定,同一个页面只能绑定一次(一个函数),
    • 想绑定多个也需要改为window.addEventListener(‘load’, function () {})DOM2绑定方式
  • -2)$(document).ready()

    • 采用的是DOM2事件绑定,监听DOMContentLoaded事件实现的,是页面DOM结构渲染完成【执行】
    • 同一个页面中可以使用多次(绑定不同的方法,因为基于DOM2事件池绑定机制完成的)

      5、DOM0 和 DOM2 的传播的区别

  • DOM0 绑定的方法,只能在目标阶段和冒泡阶段触发执行

  • DOM2绑定的方法,我们可以控制在捕获阶段执行【事件传播机制捕获】
    • 元素.addEventListener(事件类型,绑定的函数体,true/false)
    • 第三个参数:不写 默认是 false
      • false:表示从冒泡阶段触发
      • true:表示从捕获阶段触发

HTML禁止事件

oncontextmenu='return false'//禁止右键
ondragstart='return false' 
onselectstart ='return false' //禁止选中
onselect='document.selection.empty()' //把选中的内容清空
oncopy='document.selection.empty()' //把选中的内容清空
onbeforecopy='return false' 
onmouseup='document.selection.empty()'