JavaScript 进阶(三)-- 事件高级
目录
1. 注册事件( 绑定事件 )
1.1 注册事件概述
1.2 addEventListener 事件监听方式
1.3 attachEvent 事件监听方式
1.4 注册事件兼容性解决方案
2. 删除事件(解绑事件 )
2.1 删除事件的方式
2.1.1 传统注册方式
2.1.2 方法监听注册方式
2.2 删除事件兼容性解决方案
3. DOM 事件流
4. 事件对象
4.1 什么是事件对象
4.2 事件对象的使用语法
4.3 事件对象的兼容性方案
4.4 事件对象的常见属性和方法
5. 阻止事件冒泡
5.1 阻止事件冒泡的两种方式
阻止事件冒泡
5.2 阻止事件冒泡的兼容性解决方案
6. 事件委托( 代理、委派 )
事件委托
事件委托的原理
事件委托的作用
7. 常用的鼠标事件
7.1 常用的鼠标事件
7.1.1 禁止鼠标右键菜单
7.1.2 禁止鼠标选中( selectstart 开始选中 )
7.2 鼠标事件对象
案例:跟随鼠标的天使
案例分析
8. 常用的键盘事件
8.1 常用键盘事件
8.2 键盘事件对象
8.3 ASCII 表
案例:模拟京东按键输入内容
案例分析
案例:模拟京东快递单号查询
案例分析
1. 注册事件( 绑定事件 )
1.1 注册事件概述
给元素添加事件,称为注册事件或者绑定事件。
注册事件有两种方式:传统方式和方法监听注册方式
var btns = document.querySelectorAll('button'); // 1. 传统方式注册事件 btns[0].onclick = function() { alert('hi~'); } btns[0].onclick = function() { alert('how are you'); }
1.2 addEventListener 事件监听方式
eventTarget.addEventListener() 方法将指定的监听器注册到 eventTarget ( 目标对象 )上,当该对象触发指定的事件时,就会执行事件处理函数。
该方法接收三个参数:
● type:事件类型字符串,比如 click、mouseover ,注意这里不要带 on
● listener:事件处理函数,事件发生时,会调用该监听函数
● useCapture:可选参数,是一个布尔值,默认是 false。学完 DOM 事件流后,我们再进一步学习
var btns = document.querySelectorAll('button'); // 2. 事件监听注册事件 addEventListener // (1) 里面的事件类型是字符串 必定加引号 而且不带on // (2) 同一个元素同一个事件可以添加多个侦听器(事件处理程序) btns[1].addEventListener('click', function() { alert(22); }) btns[1].addEventListener('click', function() { alert(33); })
1.3 attachEvent 事件监听方式
eventTarget.attachEvent() 方法将指定的监听器注册到 eventTarget ( 目标对象 )上,当该对象触发指定的事件时,指定的回调函数就会被执行。
该方法接收两个参数:
● eventNameWithOn:事件类型字符串,比如 onclick、onmouseover ,这里要带 on
● callback:事件处理函数,当目标触发事件时回调函数被调用
注意:IE8 及早期版本支持
var btns = document.querySelectorAll('button'); // 3. attachEvent() IE9 以前的版本支持 btns[2].attachEvent('onclick', function() { alert('IE9'); })
1.4 注册事件兼容性解决方案
兼容性处理的原则:首先照顾大多数浏览器,再处理特殊浏览器
2. 删除事件(解绑事件 )
2.1 删除事件的方式
2.1.1 传统注册方式
eventTarget.onclick = null;
1 2 3 var divs = document.querySelectorAll('div'); divs[0].onclick = function() { alert(11); // 1. 传统方式删除事件 divs[0].onclick = null; }
2.1.2 方法监听注册方式
① eventTarget.removeEventListener(type,listener[,useCapture] );
② eventTarget.detachEvent(eventNamewithon,callback);
1 2 3 var divs = document.querySelectorAll('div'); // 2. removeEventListener 删除事件 divs[1].addEventListener('click', fn); // 里面的 fn 不需要调用加小括号 function fn() { alert(22); divs[1].removeEventListener('click', fn); } // 3. IE9 前 删除事件 divs[2].attachEvent('onclick', fn1); function fn1() { alert(33); divs[2].detachchEvent('onclick', fn1); }
2.2 删除事件兼容性解决方案
3. DOM 事件流
事件流描述的是从页面中接收事件的顺序。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM事件流。
比如我们给一个div注册了点击事件:
● 事件冒泡:IE 最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点的过程。
● 事件捕获:网景最早提出,由 DOM 最顶层节点开始,然后逐级向下传播到到最具体的元素接收的过程。
我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点(最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。
注意
1. JS 代码中只能执行捕获或者冒泡其中的一个阶段。
2. onclick 和 attachEvent 只能得到冒泡阶段。
3. addEventListener(type,listener[,useCapture]) 第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false( 不写默认就是 false ),表示在事件冒泡阶段调用事仲处理程序。
4. 实际开发中我们很少使用事件捕获,我们更关注事件冒泡。
5. 有些事件是没有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave
6. 事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事件,我们后面讲解。
Document .father { position: relative; margin: auto; margin-top: 200px; width: 300px; height: 300px; background-color: pink; } .father .son { position: absolute; top: 50px; left: 50px; width: 200px; height: 200px; background-color: purple; text-align: center; line-height: 200px; } son盒子 // DOM 事件流 三个阶段 // 1. JS 代码中只能执行捕获或者冒泡其中的一个阶段。 // 2. onclick 和 attachEvent (IE) 只能得到冒泡阶段。 // 3. 捕获阶段 如果 addEventListener 第三个参数是 true 那么则处于捕获阶段 document -> html -> body -> father -> son // var son = document.querySelector('.son'); // son.addEventListener('click',function() { // alert('son'); // },true); // var father = document.querySelector('.father'); // father.addEventListener('click',function() { // alert('father'); // },true); // 4. 冒泡阶段 如果 addEventListener 第三个参数是 false 或者 省略 那么则处于冒泡阶段 son -> father -> body -> html -> document var son = document.querySelector('.son'); son.addEventListener('click',function() { alert('son'); }, false); var father = document.querySelector('.father'); father.addEventListener('click',function() { alert('father'); }, false); document.addEventListener('click', function() { alert('document'); });
4. 事件对象
4.1 什么是事件对象
官方解释:event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态。
简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象 event,它有很多属性和方法。
比如:
1. 谁绑定了这个事件。
2. 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
3. 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
4.2 事件对象的使用语法
这个 event 是个形参,系统帮我们设定为事件对象,不需要传递实参过去。
当我们注册事件时,event 对象就会被系统自动创建,并依次传递给事件监听器( 事件处理函数 )。
4.3 事件对象的兼容性方案
事件对象本身的获取存在兼容问题:
1. 标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。
2. 在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。
解决:
e = e ll window .event;
Document div { width: 100px; height: 100px; background-color: pink; } 123 // 事件对象 var div = document.querySelector('div'); div.onclick = function(event) { // console.log(event); // console.log(window.event); // event = event || window.event; console.log(event); } // div.addEventListener('click', function(event) { // console.log(event); // }); // 1. event就是一个事件对象写到我们侦听函数的小括号里面当形参来看 // 2.事件对象只有有了事件才会存在,它是系统给我们自动创建的,不需要我们传递参数 // 3.事件对象是我们事件的一系列相关数据的集合跟事件相关的比如鼠标点击里面就包含了鼠标的相关信息,鼠标坐标啊,如果是键盘事件里面就包含的键盘事件的信息比如判断用户按下了那个键 // 4. 这个事件对象我们可以自己命名比如 event、evt、e // 5.事件对象也有兼容性问题 ie678 通过 window.event 兼容性写法 event = event || window.event;
4.4 事件对象的常见属性和方法
Document div { width: 100px; height: 100px; background-color: pink; } 123 - abc
- abc
- abc
// 事件对象的常见属性和方法 // 1. e.target 返回的是触发事件的对象(元素) this 返回的是绑定事件的对象(元素) // 区别: e.target点击了哪个元素,就返回哪个元素 this 哪个元素绑定了这个点击事件,那么就返回谁 var div = document.querySelector('div'); div.addEventListener('click', function(e) { console.log(e.target); // 123 console.log(this); // 123 }); var ul = document.querySelector('ul'); ul.addEventListener('click', function(e) { // 我们给ul绑定了事件那么this就指向ul console.log(this); // ...
console.log(e.currentTarget); // ...
// e.target 指向我们点击的那个对象 谁触发了这个事件 我们点击的是li e.target 指向的就是1i console.log(e.target); // ... }); // 了解兼容性 // div.onclick = function(e) { // e = e || window.event; // var target = e.target || e.srcElement; // console.log(target); // } // 2.了解跟this有个非常相似的属性 currentTarget ie678不认识
123 百度 // 常见事件对象的属性和方法 // 1. 返回事件类型 var div = document.querySelector('div'); div.addEventListener('click', fn); div.addEventListener('mouseover', fn); div.addEventListener('mouseout', fn); function fn(e) { console.log(e.type); } // 2. 阻止默认行为(事件) 让链接不跳转 或者让提交按钮不提交 var a = document.querySelector('a'); a.addEventListener('click',function(e) { e.preventDefault(); // DOM 标准写法 }); // 3. 传统的注册方式 a.onclick = function() { // 普通浏览器 e.preventDefault(); 方法 e.preventDefault(); // 低版本浏览器 IE678 returnValue 属性 e.returnValue; // 我们可以利用return false 也能阻止默认行为 没有兼容性问题 特点: return后面的代码不执行了,而且只限于传统的注册方式 // return false; // alert(11); }
5. 阻止事件冒泡
5.1 阻止事件冒泡的两种方式
事件冒泡:开始时由最具体的元素接收,然后逐级向上传播到 DOM 最顶层节点。
事件冒泡本身的特性,会带来坏处,也会带来好处,需要我们灵活掌握。
阻止事件冒泡
标准写法:利用事件对象里面的 stopPropagation() 方法
非标准写法:IE6-8 利用事件对象 cancelBubble 属性
5.2 阻止事件冒泡的兼容性解决方案
Document .father { position: relative; margin: auto; margin-top: 200px; width: 300px; height: 300px; background-color: pink; } .father .son { position: absolute; top: 50px; left: 50px; width: 200px; height: 200px; background-color: purple; text-align: center; line-height: 200px; } son盒子 // 常见事件对象的属性和方法 // 阻止冒泡 DOM 推荐的标准 stopPropagation() var son = document.querySelector('.son'); son.addEventListener('click',function(e) { alert('son'); e.stopPropagation(); // stop 停止 Propagation 传播 e.cancelBubble = true; // cancel 取消 bubble 泡泡 }, false); var father = document.querySelector('.father'); father.addEventListener('click',function() { alert('father'); }, false); document.addEventListener('click', function() { alert('document'); });
6. 事件委托( 代理、委派 )
事件冒泡本身的特性,会带来坏处,也会带来好处,需要我们灵活掌握。生活中有如下场景:
咱们班有 100 个学生,快递员有 100 个快递,如果一个个的送花费时间较长。同时每个学生领取的时候,也需要排队领取,也花费时间较长,何如?
解决方案:快递员把 100 个快递,委托给班主任,班主任把这些快递放到办公室,同学们下课自行领取即呵。
优势:快递员省事,委托给班主任就可以走了。同学们领取也方便,因为相信班主任。
事件冒泡本身的特性,会带来坏处,也会带来好处,需要我们灵活掌握。程序中也有如此场景:
点击每个 li 都会弹出对话框,以前需要给每个 li 注册事件,是非常辛苦的,而且访问 DOM 的次数越多,这就会延长整个页面的交互就绪时间。
事件委托
事件委托也称为事件代理,在 jQuery 里面称为事件委派。
事件委托的原理
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。
以上案例:给 ul 注册点击事件,然后利用事件对象的 target 来找到当前点击的 li ,因为点击 li ,事件会冒泡到 ul 上,ul 有注册事件,就会触发事件监听器。
事件委托的作用
我们只操作了一次 DOM ,提高了程序的性能。
- 知否知否,点我应有弹窗在手
- 知否知否,点我应有弹窗在手
- 知否知否,点我应有弹窗在手
- 知否知否,点我应有弹窗在手
- 知否知否,点我应有弹窗在手
// 事件委托的核心原理:给父节点添加侦听器,利用事件冒泡影响每一个子节点 var ul = document.querySelector('ul'); ul.addEventListener('click', function(e) { // alert('知否知否,点我应有弹窗在手'); // e.target 这个可以得到我们点击的对象 e.target.style.backgroundColor = 'pink'; });
7. 常用的鼠标事件
7.1 常用的鼠标事件
7.1.1 禁止鼠标右键菜单
contextmenu 主要控制应该何时显示上下文菜单,主要用于程序员取消默认的上下文菜单
我是一段不愿分享的文字 // 1. contextmenu 我们可以禁用右键菜单 document.addEventListener('contextmenu', function(e) { e.preventDefault(); })
7.1.2 禁止鼠标选中( selectstart 开始选中 )
我是一段不愿分享的文字 // 2. 禁止选中文字 selectstart document.addEventListener('selectstart', function(e) { e.preventDefault(); })
7.2 鼠标事件对象
event 对象代表事件的状态,跟事件相关的一系列信息的集合。现阶段我们主要是用鼠标事件对象 MouseEvent 和键盘事件对象 KeyboardEvent。
Document body { height: 3000px; } // 鼠标事件对象 MouseEvent document.addEventListener('click', function(e) { // 1. client 鼠标在可视区的 x 和 y 坐标 console.log(e.clientX); console.log(e.clientY); // 2. page 鼠标在页面文档的 x 和 y 坐标 console.log(e.pageX); console.log(e.pageY); // 3. screen 鼠标在电脑屏幕的 x 和 y 坐标 console.log(e.screenX); console.log(e.screenY); });
案例:跟随鼠标的天使
这个天使图片一直跟随鼠标移动
案例分析
① 鼠标不断的移动,使用鼠标移动事件:mousemove
② 在页面中移动,给 document 注册事件
③ 图片要移动距离,而且不占位置,我们使用绝对定位即可
④ 核心原理:每次鼠标移动,我们都会获得最新的鼠标坐标,把这个 x 和 y 坐标做为图片的 top 和 left 值就可以移动图片
Document img { position: absolute; } var img = document.querySelector('img'); document.addEventListener('mousemove', function(e) { // 1. mousemove只要我们鼠标移动1px就会触发这个事件 // console.log(1); // 2. 核心原理:每次鼠标移动,我们都会获得最新的鼠标坐标,把这个 x 和 y 坐标做为图片的 top 和 left 值就可以移动图片 var x = e.pageX; var y = e.pageY; console.log('x坐标是:' + x + ', y坐标是:' + y); // 3. 千万不要忘记给 left 和 top 添加 px 单位 img.style.top = y - 40 + 'px'; img.style.left = x - 33 + 'px'; })
8. 常用的键盘事件
8.1 常用键盘事件
事件除了使用鼠标触发,还可以使用键盘触发。
注意:
1. 如果使用 addEventListener 不需要加 on
2. onkeypress 和前面 2 个的区别是,它不识别功能键,比如左右箭头,shift等。
3. 三个事件的执行顺序是:keydown -- keypress --- keyup
// 常用的键盘事件 // 1. keyup 按键弹起时触发 // document.onkeyup = function() { // console.log('我弹起了'); // } document.addEventListener('keyup' , function() { console.log('我弹起了'); }) // 2. keydown 按键按下时触发 能识别功能键比如ctrl shift左右箭头啊 document.addEventListener('keydown' , function() { console.log('我按下了 - keydown'); }) // 3. keypress 按键按下时触发 不能识别功能键比如ctrl shift左右箭头啊 document.addEventListener('keypress' , function() { console.log('我按下了 - keypress'); }) // 4. 三个事件的执行顺序 keydown -- keypress -- keyup
8.2 键盘事件对象
注意:onkeydown 和 onkeyup 不区分字母大小写,onkeypress 区分字母大小写。
在我们实际开发中,我们更多的使用 keydown 和 keyup,它能识别所有的键(包括功能键)
Keypress 不识别功能键,但是 keyCode 属性能区分大小写,返回不同的 ASCII 值
8.3 ASCII 表
// 键盘事件对象中的 keyCode 属性可以得到相应键的 ASCII 码值 // 1.我们的 keyup 和 keydown 事件不区分字母大小写 a 和 A 得到的都是 65 // 2.我们的 keypress 事件区分字母大小写 a 97 和 A 得到的是 65 document.addEventListener('keyup', function(e) { // console.log(e); console.log('keyup: ' + e.keyCode); //我们可以利用 keycode 返回的 ASCII 码值来判断用户按下了那个键 if (e.keyCode === 65){ alert( '您按下的a键'); }else { alert( '您没有按下a键'); } }) document.addEventListener('keypress', function(e) { // console.log(e); console.log('keypress: ' + e.keyCode); })
案例:模拟京东按键输入内容
当我们按下 s 键,光标就定位到搜索框
案例分析
① 核心思路:检测用户是否按下了 s 键,如果按下 s 键,就把光标定位到搜索框里面
② 使用键盘事件对象里面的 keyCode 判断用户按下的是否是 s 键
③ 搜索框获得焦点:使用 js 里面的 focus() 方法
// ① 核心思路:检测用户是否按下了 s 键,如果按下 s 键,就把光标定位到搜索框里面 // ② 使用键盘事件对象里面的 keyCode 判断用户按下的是否是 s 键 // ③ 搜索框获得焦点∶使用js 里面的 focus()方法 var search = document.querySelector('input'); document.addEventListener('keyup', function(e) { if (e.keyCode === 83) { search.focus(); } else {} });
案例:模拟京东快递单号查询
要求:当我们在文本框中输入内容时,文本框上面自动显示大字号的内容。
案例分析
① 快递单号输入内容时,上面的大号字体盒子( con )显示( 这里面的字号更大 )
② 表单检测用户输入:给表单添加键盘事件
③ 同时把快递单号里面的值( value )获取过来赋值给 con 盒子( innerText )做为内容
④ 如果快递单号里面内容为空,则隐藏大号字体盒子( con )盒子
⑤ 注意:keydown 和 keypress 在文本框里面的特点:他们两个事件触发的时候,文字还没有落入文本框中。
⑥ keyup 事件触发的时候,文字已经落入文本框里面了
⑦ 当我们失去焦点,就隐藏这个 con 盒子
⑧ 当我们获得焦点,并且文本框内容不为空,就显示这个 con 盒子
Document * { margin: 0; padding: 0; } .search { position: relative; width: 178px; margin: 100px; } .con { display: none; position: absolute; top: -40px; width: 171px; border: 1px solid rgba(0, 0, 0, .2); box-shadow: 0 2px 4px rgba(0, 0, 0, .2); padding: 5px 0; font-size: 18px; line-height: 20px; color: #333; } .con::before { content: ''; width: 0; height: 0; position: absolute; top: 28px; left: 18px; border: 8px solid #000; border-style: solid dashed dashed; border-color: #fff transparent transparent; } 123 // 快递单号输入内容时,上面的大号字体盒子(con)显示(这里面的字号更大) // 表单检测用户输入:给表单添加键盘事件 // 同时把快递单号里面的值(value)获取过来赋值给 con盒子(innerText)做为内容 // 如果快递单号里面内容为空,则隐藏大号字体盒子(con)盒子 var con = document.querySelector('.con'); var jd_input = document.querySelector('.jd'); jd_input.addEventListener('keyup', function() { // console.log('输入内容啦'); if (this.value == '') { con.style.display = 'none'; } else { con.style.display = 'block'; con.innerHTML = this.value; } }) // 当我们失去焦点,就隐藏这个 con 盒子 jd_input.addEventListener('blur', function() { con.style.display = 'none'; }) // 当我们获得焦点,并且文本框内容不为空,就显示这个 con 盒子 jd_input.addEventListener('focus', function() { if (this.value != '') { con.style.display = 'block'; } })