DOM 事件详解

注册事件(绑定事件)

1. 概述

给元素添加事件,称为注册事件 / 绑定事件

注册事件有两种方式:传统方式和方法监听注册方式

2. 传统注册方式

利用 on 开头的事件

DOM 0级事件绑定方式—— 因为 W3C 在确定 DOM 版本时,在之前已经有了事实上存在的一些事件,比如 onclick 这种直接绑定给元素的属性的事件,DOM1 中用的还是之前的事件,所以是 DOM 0级事件,而不是1级事件

<button oncilck = "alert(1)"></button>

btn.onclick = function () {}

特点:注册事件的唯一性(同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数)

3. 方法监听注册方式

DOM 2级事件绑定方式

element.addEventListener() 方法

element.attachEvent() 方法

特点:同一个元素可以多次绑定事件监听,同一个事件类型可以注册多个事件函数

(1)element.addEventListener() 方法

  • 第一个参数:事件类型的字符串(直接书写” click” ,不需要加 on
  • 第二个参数:事件处理函数,事件发生时,会调用该监听函数

W3C 标准,推荐方式

兼容性问题:不支持 IE9 以下的浏览器

var btn = document.getElementById("btn");
btn.addEventListener("click", function () {
  alert(1);
});
// 多次绑定相同的事件类型,事件会根据书写的顺序进行一个事件排队
btn.addEventListener("click", clickEvent);
function clickEvent() {
  alert(2);
}
// 点击按钮,先弹出1,再弹出2

(2)element.attachEvent() 方法

  • 第一个参数:事件类型的字符串(需要加 on
  • 第二个参数:事件函数,当目标触发事件时回调函数被调用

非标准,尽量不使用

兼容性问题:只支持 IE10 及以下的浏览器

var btn = document.getElementById("btn");
btn.attachEvent("onclick", function () {
  alert(3);
});
btn.attachEvent("onclick", clickEvent);
function clickEvent() {
  alert(4);
}
// IE8 及以下的浏览器处理事件队列时,会出现顺序错乱

4. 注册事件的兼容写法

自定义一个注册事件函数

  • 参数:事件源,事件类型(不加 on),事件函数
  • IE9 及以上的浏览器,使用 addEventListener 方法
  • IE9 以下的浏览器,使用 attachEvent 方法

判断浏览器时,不需要判断它的版本,可以检测浏览器能力

浏览器能力检测:将某个方法的调用作为 if 语句的判断条件,如果浏览器认识该方法返回 true,否则返回 false

var btn = document.getElementById("btn");
// 调用函数
addEvent(btn, "click", function () {
	alert(1);
});
// DOM 2级事件绑定方式
// 自己制作一个兼容所有浏览器的绑定事件的函数
// 参数:事件源,事件类型,事件函数
function addEvent(ele, type, fn) {
	// IE9 及以上浏览器和其他浏览器,使用 addEventListener 方法
    // IE9 以下的浏览器,使用 attachEvent 方法
    // 浏览器能力检测
    if (ele.addEventListener) {
      	ele.addEventListener(type, fn);
    } else if (ele.attachEvent) {
      	ele.attachEvent("on" + type, fn);
    } else {
      	// 相当于 ele.onclick = fn;
    	// 这个 else 条件不写也可
      	ele["on" + type] = fn; // 传统方式
    }
}

删除事件

1. 传统注册方式

例如:eventTarget.onclick = null

var btn = document.getElementById("btn");
// DOM 0级事件绑定方式
// 传统注册方式
btn.onclick = function () {
  alert(1);
};
// 解除绑定方法
btn.onclick = null;

2. 方法监听注册方式

注意:没有办法移除一个匿名函数,所以在注册事件时需要单独声明一个有函数名的事件函数。

(1)element.removeEventListener() 方法

  • 第一个参数:事件类型的字符串(直接书写” click” ,不需要加 on)
  • 第二个参数:事件函数引用名

兼容性问题:不支持 IE9 以下的浏览器

var btn = document.getElementById("btn");
// 绑定事件
btn.addEventListener("click", fun);
// 解除绑定
btn.removeEventListener("click", fun);
function fun() {
  alert(1);
}
var btn = document.getElementById("btn");
// 绑定事件
btn.addEventListener("click", fun);
function fun() {
	alert(1);
	// 解除绑定
	btn.removeEventListener("click", fun);
}
// 点击按钮,只弹出一次1,点第二次不再弹出

(2)element.detachEvent() 方法

  • 第一个参数:事件类型的字符串(需要加 on)
  • 第二个参数:事件函数

兼容性问题:只支持 IE10 及以下的浏览器

var btn = document.getElementById("btn");
// 绑定事件
btn.attachEvent("onclick", fun);
// 解除绑定
btn.detachEvent("onclick", fun);
function fun() {
  alert(1);
}

3. 移除事件的兼容写法

自定义一个移除事件函数

  • 参数:事件源,事件类型(不加 on),事件函数
  • IE9 及以上的浏览器,使用 removeEventListener 方法
  • IE9 以下的浏览器,使用 detachEvent 方法
// 兼容所有浏览器的解除绑定事件的函数
// 参数:事件源,事件类型(不加 on),事件函数
function removeEvent(ele, type, fn) {
	// IE9 及以上浏览器和其他浏览器,使用 removeEventListener 方法
    // IE9 以下的浏览器,使用 detachEvent 方法
    // 浏览器能力检测
    if (ele.removeEventListener) {
    	ele.removeEventListener(type, fn);
    } else if (ele.detachEvent) {
        ele.detachEvent("on" + type, fn);
    } else {
        // 相当于 element.onclick = null;
        // 这个 else 条件不写也可
        element["on" + type] = null; // 传统方式
    }
}

建议:将自己封装的一些常用函数和方法,放到一个单独的 .js 文件中

DOM 事件流

在这里插入图片描述

事件流描述的是从页面中接收事件的顺序

事件发生时会在元素节点之间按照待定顺序传播,这个传播过程即 DOM 事件流

1. 事件流的三个阶段

  • 第一个阶段:事件捕获
  • 第二个阶段:事件执行过程
  • 第三个阶段:事件冒泡

事件捕获:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程

事件冒泡:IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程

注意:

(1)JS 代码中只能执行捕获 / 冒泡其中的一个阶段

(2)onclick / attachEvent() 只能得到冒泡阶段

(3)addEventListener() 第三个参数为 false(或默认)时,事件冒泡

(4)addEventListener() 第三个参数为 true 时,事件捕获

(5)实际开发中很少用事件捕获,更关注事件冒泡

(6)有些事件是没有冒泡的,比如:onblur、onfocus、onmouseenter、onmouseleave

(7)事件冒泡有时会带来麻烦,有时又会很巧妙的帮助做某些事情

<div id="father">
	<div id="son"></div>
</div>
<script>
	var father = document.getElementById("father");
	var son = document.getElementById("son");
	// 事件捕获
	father.addEventListener("click", fn1, true);
   	son.addEventListener("click", fn2, true);
    function fn1() {
    	alert("father");
    }
    function fn2() {
    	alert("son");
    }
	// 当点击 son 时,先弹出 father,再弹出 son
</script>
// 事件冒泡
father.onclick = function () {
  alert("father");
};
son.onclick = function () {
  alert("son");
};
// 当点击 son 时,先弹出 son,再弹出 father

2. 事件委托

事件委托也称事件代理,在 jQuery 里称为事件委派

利用事件冒泡,将子级的事件委托给父级加载

同时,需要利用事件函数的一个 e 参数,内部存储的是事件对象

  • 只要触发事件,函数内部都可以得到一个事件对象,对象中存储了关于事件的一系列数据
  • e.target 属性记录的就是真正触发事件的事件源

案例:ul 里包含5个 li,要想点击每个 li 都弹出对话框,以前需要循环给每个 li 注册事件,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间

事件委托的原理:不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点(即给父节点添加侦听器,利用事件冒泡影响每一个子节点

上一个案例用事件委托实现:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li,因为点击 li ,事件会冒泡到 ul 上,ul 有注册事件,就会触发事件监听器

事件委托的作用:只操作了一次 DOM,提高了程序的性能

// 让每个 li 被点击后,自己添加特殊的背景颜色,而其他兄弟不添加
// 事件委托:可以将一些子级的公共类型的事件委托给它们的父级添加,在父级内部想办法找到真正触发事件的最底层的事件源
// 获取 ul
var list = document.getElementById("list");
// 获取 ul 里所有的 li
var lis = list.children;
list.addEventListener("click", function (e) {
   	// 在内部要想办法找到真正触发事件的 li
	// 借用事件函数内部的一个参数 e,e 是事件对象
    // e.target 属性记录的就是真正触发事件的事件源
    // 排除其他
 	for (var i = 0; i < lis.length; i++) {
    	lis[i].style.backgroundColor = "";
  	}
  	e.target.style.backgroundColor = "pink";
});

3. 事件对象

(1)什么是事件对象?

eventTarget.onclick = function (e) {}
eventTarget.addEventListener("click",function (e) {})
// 这里的 e 就是事件对象,也可以写成 event / evt

只要触发事件,就会有一个对象,内部存储了与事件相关的数据。

  • 官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态
  • 简单解释:事件发生后,跟事件相关的一系列信息数据的集合都放在这个对象里面,这个对象就是事件对象 event ,它有很多属性和方法

e 在低版本浏览器中有兼容问题,低版本浏览器使用的是 window.event

兼容性的写法:e = e || window.event;

event 就是一个事件对象,写到侦听函数的小括号里,当形参来看

事件对象只有有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数

事件对象是我们事件的一系列相关数据的集合,跟事件相关的,比如鼠标点击,里面就包含了鼠标的相关信息(鼠标坐标等);若是键盘事件,里面就包含键盘事件的信息,比如判断用户按下了哪个键

事件对象也有兼容性问题,IE678 通过 window.event

(2)事件对象的常见属性和方法

e.eventPhase:查看事件触发时所处的阶段

  • 判断出事件执行时处于哪个阶段
  • 1:表示捕获阶段
  • 2:表示目标阶段
  • 3:表示冒泡阶段

e.target:用于获取触发事件的元素

  • 标准,IE678 不识别
  • 获取真正触发事件的元素
  • 例如:点击了哪个元素,就返回哪个元素

e.srcElement:用于获取触发事件的元素

  • 低版本浏览器使用
  • 非标准,IE678 识别使用
  • 兼容性写法:var target = e.target || e.srcElement;

e.currentTarget:用于获取绑定事件的事件源元素

  • IE678 无法识别
  • 和 this 非常相似,都指向绑定事件的事件源元素,一般都使用 this

e.type:获取事件类型

  • 比如:click、mouseover(不带 on)
  • 实际应用:可以将所有给一个元素绑定的事件的事件函数写在一个函数内,通过函数内部的 e.type 判断走不同的分支
// 鼠标经过变颜色,鼠标离开后恢复为原来的颜色色
box.onmouseover = fn;
box.onmouseout = fn;
function fn(e) {
  e = e || window.event;
  switch (e.type) {
    case "mouseover":
      box.style.backgroundColor = "pink";
      break;
    case "mouseout":
      box.style.backgroundColor = "yellowgreen";
      break;
  }
}

e.preventDefault():取消默认行为

  • DOM 标准写法,IE678 不识别
  • 比如不让链接跳转,不让提交按钮提交

e.returnValue:取消默认行为

  • 非标准,低版本浏览器使用
// 让 a 链接不跳转
a.onclick = function (e) {
	e = e || window.event;
    alert("hello");
    // 普通的方式阻止默认行为
    // 只限于传统注册方式使用(没有兼容问题)
    // return false;

    // DOM 方法
    // 普通浏览器使用,IE678 不识别
    // e.preventDefault();
    // 低版本浏览器需要使用一个对象的属性
    e.returnValue = false;
};

4. 阻止事件冒泡

(1)e.stopPropagation();

  • 阻止冒泡,标准方式

(2)e.cancelBubble = true;

  • 阻止冒泡
  • IE 低版本,标准中已废弃(非标准,IE678 使用)
<body>
    <div id="box1">
      <div id="box2">
        <div id="box3"></div>
      </div>
    </div>
    <script>
      var box1 = document.getElementById("box1");
      var box2 = document.getElementById("box2");
      var box3 = document.getElementById("box3");
      var array = [box1, box2, box3];
      for (var i = 0; i < array.length; i++) {
        var box = array[i];
        box.onclick = function (e) {
          e = e || window.event;
          console.log(this.id);
          // 阻止事件冒泡
          //   e.stopPropagation();
          // 低版本浏览器使用  属性
          e.cancelBubble = true;
        };
      }
    </script>
</body>
/* css 代码 */
#box1 {
	width: 200px;
    height: 200px;
    background-color: pink;
}
#box2 {
  width: 150px;
  height: 150px;
  background-color: skyblue;
}
#box3 {
  width: 100px;
  height: 100px;
  background-color: greenyellow;
}

5. 常用的鼠标事件

(1)常用的鼠标事件

contextmenu

  • 禁止鼠标右键菜单
  • 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
// 禁止鼠标右键菜单
document.addEventListener("contextmenu", function (e) {
	e.preventDefault();
});

selectstart

  • 禁止鼠标选中
// 禁止鼠标选中
document.addEventListener("selectstart", function (e) {
	e.preventDefault();
});

(2)鼠标事件对象

e.clientX/e.clientY

  • 鼠标距离浏览器窗口左上角的距离
  • 所有浏览器都支持

e.pageX/e.pageY

  • 鼠标距离整个HTML文档页面左上顶点的距离,
  • IE8 以前不支持

e.screenX/e.screenY

  • 鼠标距离电脑屏幕左上顶点的距离
  • IE8 以前不支持

案例:图片跟随鼠标移动效果

<body>
    <img src="../images/tianshi.gif" id="pic" />
    <script>
      var pic = document.getElementById("pic");
      // 通过鼠标移动事件给图片添加 left 和 top 的值
      // 给整个文档添加鼠标移动事件
      document.onmousemove = function (e) {
        e = e || window.event;
        var x = e.clientX;
        var y = e.clientY;
        // 给元素的 css 属性赋值
        pic.style.left = x - 50 + "px";
        pic.style.top = y - 40 + "px";
        // 鼠标默认显示在图片左上角
        // 其中 -50 和 -40 是减去图片长宽的一半,为了使鼠标显示在图片中心,
      };
    </script>
</body>
/* css 代码 */
* {
	margin: 0;
    padding: 0;
}
#pic {
	/* fixed 参考元素:浏览器窗口 */
    /* absolute 如果祖先都没有定位,参考body */
    position: fixed;
}

6. 常用的键盘事件

(1)常用键盘事件

onkeyup

  • 某个键盘按键被松开时触发

onkeydown

  • 某个键盘按键被按下时触发

onkeypress

  • 某个键盘按键被按下时触发
  • 但它不识别功能键(ctrl、shift、箭头等)

注意:

  • 如果使用 addEventListener 不需要加 on
  • 三个事件的执行顺序是:keydown → keypress → keyup
document.addEventListener("keydown", function () {
  console.log("我按下了");
});
document.onkeyup = function () {
  console.log("我弹起了");
};

(2)键盘事件对象

key

  • 可以得到按的是哪个键
  • 比如:按下 a ,返回 a
  • 有兼容问题

keyCode

  • 返回该键的 ASCII 值,是属性
  • keyup 和 keydown 事件不区分字母大小写,例如:a 和 A 得到的都是65
  • keypress 事件区分字母大小写,例如:a 97 和 A 65
document.addEventListener("keydown", function () {
	console.log("我按下了");
});
document.onkeyup = function () {
	console.log("我弹起了");
};

document.addEventListener("keydown", function (e) {
	console.log(e.keyCode); // 例如按键盘上的 a/A ,都返回 65
    // 可以利用 keyCode 返回的 ASCII 码值来判断用户按下了哪个键
    if (e.keyCode === 65) {
    	alert("您按下的a键");
    } else {
        alert("您没有按下a键");
    }
});

document.addEventListener("keypress", function (e) {
	console.log(e.keyCode); // 例如按键盘上的a得到97,A得到65
});
// 实际开发中,更多使用 keydown 和 keyup,它能识别所有的键(包括功能键)
// keypress 不识别功能键,但 keyCode 属性能区分大小写,返回不同的 ASCII 值

MDN web 事件参考:MDN

案例:模拟京东按键输入内容(当我们按下 s 键,光标就定位到搜索框)

核心思路:检测用户是否按下了 s 键,若按下 s 键,就把光标定位到搜索框里

搜索框获得焦点:使用 js 里的 focus() 方法

var search = document.querySelector("input");
// 用 keydown 的话,会导致 s 写入搜索框内,而 keyup 是弹起后才触发,不会写入 s
document.addEventListener("keyup", function (e) {
	// console.log(e.keyCode);
    // 按下 s 键,得出 s 的 ASCII 值是83
    if (e.keyCode === 83) {
    	search.focus();
    }
});

案例:模拟京东快递单号查询

当我们在文本框输入内容时,文本框上面自动显示大字号的内容,提高用户体验
在这里插入图片描述

<body>
    <div class="search">
      <div class="con"></div>
      <input type="text" class="jd" placeholder="请输入您的快递单号" />
    </div>
    <script>
      var con = document.querySelector(".con");
      var jd_input = document.querySelector(".jd");
      // 检测用户输入
      jd_input.addEventListener("keyup", function () {
        // 若快递单号为空,则隐藏大号字体盒子
        if (this.value == "") {
          con.style.display = "none";
        } else {
          con.style.display = "block";
          // 把快递单号里面的值获取过来赋值给 con 盒子作为内容
          con.innerText = this.value;
        }
      });
      // 失去焦点时,隐藏 con 盒子
      jd_input.addEventListener("blur", function () {
        con.style.display = "none";
      });
      // 获得焦点时,如果 innput 输入框不为空,显示 con 盒子
      jd_input.addEventListener("focus", function () {
        if (this.value !== "") {
          con.style.display = "block";
        }
      });
    </script>
</body>
/* css 代码 */
* {
  margin: 0;
  padding: 0;
}
.search {
  position: relative;
  width: 178px;
  margin: 100px;
}
.con {
  display: none;
  position: absolute;
  top: -40px;
  width: 161px;
  padding: 5px 0;
  border: 1px solid rgba(0, 0, 0, 0.2);
  font-size: 18px;
  color: #333;
  line-height: 20px;
  box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2);
}
.con::before {
  content: "";
  position: absolute;
  top: 28px;
  left: 18px;
  width: 0;
  height: 0;
  border: 8px solid #000;
  border-style: solid dashed dashed;
  border-color: #fff transparent transparent;
}

注意:

  • keydown 和 keypress 在文本框里面的特点:它们两个事件触发的时候,文字还没有落入文本框中
  • keyup 事件触发的时候,文本已经落入文本框里面了
```
/* css 代码 */
* {
  margin: 0;
  padding: 0;
}
.search {
  position: relative;
  width: 178px;
  margin: 100px;
}
.con {
  display: none;
  position: absolute;
  top: -40px;
  width: 161px;
  padding: 5px 0;
  border: 1px solid rgba(0, 0, 0, 0.2);
  font-size: 18px;
  color: #333;
  line-height: 20px;
  box-shadow: 0 2px 4px rgb(0, 0, 0, 0.2);
}
.con::before {
  content: "";
  position: absolute;
  top: 28px;
  left: 18px;
  width: 0;
  height: 0;
  border: 8px solid #000;
  border-style: solid dashed dashed;
  border-color: #fff transparent transparent;
}

注意:

  • keydown 和 keypress 在文本框里面的特点:它们两个事件触发的时候,文字还没有落入文本框中
  • keyup 事件触发的时候,文本已经落入文本框里面了
  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值