事件
一、事件绑定方式
-
HTML标签属性绑定
-
优点:结构简单,代码简洁
-
缺点:
- 01:JS混在HTML中写,不方便维护
- 02:绑定的事件处理函数,必须是全局变量,有变量名污染的风险。
- 03:内部获取this需要通过传参才能拿到,直接获取只能拿到window,操作不便。
- 04:JS以事件为驱动的设计场景下,想实现事件处理函数的组合,代码结构复杂。
-
举例
<body> <button onclick='btn()'>点击</button> </body> <script> function fn(){ alert('被点击了') } //删除方式 el.setAttribute('onclick',''); </script>
-
-
通过DOM元素节点属性绑定
-
优点:
-
JS和HTML分离,方便维护
-
分割作用域,放置变量名污染
-
内部的this可以直接获取到节点对象,不需要传参
-
-
缺点:不能实现多个事件处理函数的叠加绑定,后面绑定的会覆盖前面的。JS的事件处理函数设计内部代码会比较冗长。结构相对复杂。
-
应用场景:一般主要使用的绑定方式,可以用在任何需要绑定事件的场景。
-
删除事件:node.onType = null;
-
举例
<body> <button id="btn"></button> </body> <script> //方式一 btn.onclick = function(){} //方式二 btn.onclick = fn; function fn() </script>
-
-
事件侦听器绑定事件
-
优点:
-
JS和HTML分离
-
不受作用域限制
-
内部this指向调用对象
-
可以动态添加、动态删除事件处理函数,方便设计灵活的事件处理程序
-
-
缺点:Ie8以下不支持,需要编写额外的兼容代码
-
其他特点:
-
通过addEventListener绑定多个事件时,执行顺序与绑定顺序保持一致
-
通过addEventListener多次绑定相同的事件(相同事件:必须三个参数都一致),后面书写的会被丢弃,只有一个生效。
-
通过addEventListener绑定多个事件时,只要三个参数中有一个参数不一样,就是新创建了一个侦听器。
-
在删除侦听事件处理函数时,通过removeEventListener(‘type’,函数名,true);三个参数必须给定,如果通过匿名函数绑定的侦听器,是不能被删除的,如果需要能够删除,那就要用命名函数进行函数绑定。
-
-
举例应用
<body> <button onclick='btn'>点击</button> <button onclick='btn1'>点赞</button> <button onclick='btn2'>删除事件</button> <button onclick='btn3'></button> </body> <script> /* 参数一:"click" 参数二:匿名函数 || 命名函数 || 对象 */ //两个侦听绑定函数之间传参 btn.addEventListener('click',function(){ alert('被点击了'); var name = '张三'; this.name = name;//给btn按钮添加name属性 }) btn.addEventListener('click',function(){ this.innerHTML = this.name;//将btn name属性的值拿出来。放到按钮中 }) /*****************************************************/ //实现点赞效果 btn1.addEventListener('click',zan);//这里如果写true或者false,下面删除的地方也必须写上对应的true或false function zan(){ this.innerHTML = '已赞'; this.removeEventListener('click',zan);//函数最后,将该事件删除 } /*****************************************************/ //当删除事件时,三个参数必须相同 btn2.addEventListener('click',fn1,true);//1标记 btn2.addEventListener('click',fn1,false);//2标记 function fn1(){ alert('111'); } //删除事件 btn2.removeEventListener('click',fn1,true);//删除的是1标记 /*****************************************************/ //拓展:addEventLisenter的第二个参数除了可以是匿名函数、命名函数外,还可以是对象 var obj = { handleEvent : function(){ console.log(this); //指向window alert('dddd') } } btn3.addEventListener('click',obj); </script>
-
二、事件侦听器兼容写法
-
ie9以上写法
el.addEventListener('click',fn(,true))
-
ie8及以下写法
el.detachEvent('onclick',fn)
-
兼容写法
function bindEvent(el, type, callback) { if (el.addEventListener) { //IE9以上, 其他非IE浏览器 el.addEventListener(type, callback) } else { // IE8 及以下 el.attachEvent('on' + type, callback) } } function removeEvent(el, type, callback) { if (el.removeEventListener) { //非IE8以下 el.removeEventListener(type, callback) } else { //IE8以下 el.detachEvent('on' + type, callback) } } //调用 function dd() { alert('按钮被点击了'); } bindEvent(btn1, 'click', dd); removeEvent(btn1, 'click', dd);
IE8 使用 attachEvent绑定事件与 addEventListener的区别:
1、attachEvent只支持两个参数,等效于addEventKListener第三参数为false.
2、attachEvent注册事件与事件处理函数的执行顺序相反,先注册的后执行
3、attachEvent注册的事件处理函数内部,this总是指向window
三、事件的传播流
-
事件的传播流:
1、浏览器时刻都在发生着事件流,和是否绑定了事件处理函数无关。
2、事件流分为三个阶段
阶段一:事件捕获阶段:在事件发生后,浏览器从根标签开始,一层一层向下查找,直到找到触发事件的节点对象。这个阶段就是查找事件触发节点对象的过程
阶段二:事件的目标阶段:找到触发事件的目标节点后,记录事件类型,判断目标是否有该类型事件的处理函数,如果有执行。
阶段三:事件的传播阶段(事件冒泡阶段):目标阶段结束后,将该类型的事件向上一层一层往回传播,每层节点对象都会判断是否绑定了该种类型事件的处理函数,如果绑定了就执行,执行结束后,接着向上传播,直到根节点结束。
-
事件流引发的两个问题:
1、阻止事件冒泡:在父子级嵌套情况下,都绑定了相同类型的事件处理函数,为了保证各自执行各自的,需要阻止事件冒泡。
2、事件代理:利用事件冒泡,可以将子级的事件处理函数,绑定在父级的同类型事件处理函数内
-
事件代理的优缺点:
1、优点:
(1)不用给新增的标签节点绑定事件
(2)减少事件注册,节省内存 2、缺点:
(1)事件绑定不直观,如果代码被二次接手或者太长时间没有接触项目,无法再浏览器的事件侦听那里直接观察到事件绑定。
(2)事件代理基于冒泡,如果内层结构阻止冒泡,事件代理就会失效。
事件代理代码
<body>
<div class="box" id="box">
<div class="box1">box1</div>
<div class="box2">box2</div>
</div>
</body>
<script>
box.addEventListener('click',function(e){
switch(e.target.className){
case 'box1' :
box1Event(e.target);
break;
case 'box2' :
box2Event(e.target);
break;
}
})
function box1Event(el){
console.log(el);
}
function box2Event(el){
console.log(el);
}
</script>
事件代理进一步封装
<body>
<div class="box" id="box">
<div class="box1">box1</div>
<div class="box2">box2</div>
</div>
</body>
<script>
// bindDelegate(父级,事件类型,'.box3',callback)
box.addEventListener('click',function(e){
// e.target : 触发事件的目标对象
if(typeof this[e.target.className] == 'function')
this[e.target.className](e.target)
})
box.box1 = function(el){
console.log(el);
}
box.box2 = function(el){
console.log(el);
}
</script>
四、事件对象
事件对象:在用户触发事件时。传入事件对象,会向事件处理函数传递一个对象数据,该对象数据就称为事件对象,不同类型的事件,传入的事件对象各有差异,通过该事件对象,我们可以对事件进行精准控制,可以扩展事件处理函数功能,可以阻止事件冒泡,可以进行事件代理。
-
基本获取方法
-
btn.onclick = function(e){ console.log('我拿到e(事件对象)了') }
-
-
ie8写法
-
btn.onclick = function(e){ //IE8不支持在事件调用时,传入事件对象,但是有一个全局变量event会被赋值成对应的事件对象。 console.log(window.event) console.log('我拿到e(事件对象)了') }
-
-
兼容写法
-
<body> <button id='btn1'>点击</button> <button id='btn2'>点击二</button> <button onclick='fn(event)'>点击三</button> </body> <script> btn1.onclick = function(e){ //e event evt 可能的名称 e = e || window.event; //兼容IE8 //IE8不支持事件调用时,传入事件对象, 但是有一个全局变量 event会被赋值成对应的事件对象,因此需要写兼容 console.log(e);//能拿到 } btn2.addEventListener('click',function(e){ console.log(e);//能拿到 }) function fn(e){ console.log(e);//能拿到 } </script>
-
五、阻止冒泡事件
1.阻止向上传递冒泡事件
-
非ie8写法
e.stopPropagation();
-
ie8写法(目前谷歌等其他浏览器也能使用)
e.cancelBubble = true;
-
兼容写法
// 阻止事件冒泡
if(e.stopPropagation){
e.stopPropagation(); //非ie8
}else{
e.cancelBubble = true;//ie8
}
2.阻止当前节点其他事件
-
DOM3定义事件冒泡阻止方法,可以处理阻止事件传播以外,还可以阻止当前节点其他事件
e.stopImmediatePropagation();
-
示例代码
-
<body> <div id="box1"> <div id="box2"></div> </div> <div id="box3"> <div id="box4"></div> </div> </body> <script> box1.onclick = function(){ alert('box1'); } box2.onclick = function(e){ //兼容ie写法 e = e || window.event; // 阻止事件冒泡 if(e.stopPropagation){ e.stopPropagation(); //非ie8 }else{ e.cancelBubble = true;//ie8 } // e.stopPropagation(); alert('box2'); } box3.addEventListener('click',function(){ alert("box3") }) //给box4添加两个事件,因为 e.stopImmediatePropagation(),只会出现当前事件 box4.addEventListener('click',function(e){ e = e || window.event; // 阻止事件传播,还可以阻止当前节点其他事件 e.stopImmediatePropagation(); alert("box4") }) box4.addEventListener('click',function(){ alert("box44") }) </script>
六、阻止系统默认事件
-
非ie8写法
e.preventDefault()
-
ie8写法
e.returnValue = false
-
兼容写法
if(e.preventDefault){ e.preventDefault();//非ie8写法 }else{ e.returnValue = false; //ie8写法 }
-
只能给标签绑定点击事件的时候使用(node.ontype )
return false;
- 侦听器绑定事件示例
bindEvent(b,'click',function(e){ //事件传播流兼容 e = e || window.event if(e.preventDefault){ e.preventDefault(); //非ie8 }else{ e.returnValue = false; //ie8 其他高级非ie8浏览器 } }) //处理侦听器绑定事件兼容 function bindEvent(el, type, callback) { if (el.addEventListener) { //IE9以上, 其他非IE浏览器 el.addEventListener(type, callback) } else { // IE8 及以下 el.attachEvent('on' + type, callback) } }
- 行间阻止系统默认事件
function fn(e){ if(e.preventDefault){ e.preventDefault(); }else{ e.returnValue = false; } console.log(11111); }
- 标签对象绑定事件阻止系统默认事件
a.onclick = function(e){ e = e || window.event console.log("1111"); return false; //只能标签对象绑定事件能够使用 }
七、事件代理
*将事件绑定在父级标签上,通过e.target(触发事件的目标对象)*获得当前点击的按钮,采用给父级标签添加属性,属性中绑定函数的方法,进行事件绑定。
box.addEventListener('click',function(e){
// e.target : 触发事件的目标对象
this[e.target.className](e.target)
})
box.box1 = function(el){
console.log(el);
}
box.box2 = function(el){
console.log(el);
}
八、文档加载事件
-
监测文档及文档中资源加载完毕
window.addEventListener('load',function(){ console.log('文档及资源加载完毕'); })
-
监测文档加载完毕
window.addEventListener('DOMContentLoaded',function(){ console.log('文档加载完毕'); })
九、鼠标事件类型
-
单击事件 onclick
<body> <button id='btn1'>单击</button> </body> <script> btn1.addEventListener('click',function(){ alert('单击'); }) </script>
-
双击事件 ondblclick
<body> <button id='btn2'>双击</button> </body> <script> btn2.addEventListener('dblclick',function(){ alert('双击'); }) </script>
-
鼠标按下 onmousedown
<body> <button id='btn3'>鼠标按下</button> </body> <script> btn3.addEventListener('mousedown',function(){ alert('鼠标按下'); }) </script>
-
鼠标抬起来 onmouseup
<body> <button id='btn4'>鼠标抬起</button> </body> <script> btn4.addEventListener('mouseup',function(){ alert('鼠标抬起'); }) </script>
-
鼠标移入 onmouseover
<body> <button id='btn5'>鼠标移入</button> </body> <script> btn5.addEventListener('mouseover',function(){ alert('鼠标移入'); }) </script>
-
鼠标移出 onmouseout
<body> <button id='btn6'>鼠标移出</button> </body> <script> btn6.addEventListener('mouseout',function(){ alert('鼠标移出'); }) </script>
-
不带冒泡鼠标移入和移出 onmouseenter(移入) onmouseleave(移出)
<body> <div id='box'> <button id='btn7'>不带冒泡鼠标移入</button> <button id='btn8'>不带冒泡鼠标移出</button> </div> </body> <script> box.addEventListener('mouseover',function(){ alert('鼠标移入'); }) box.addEventListener('mouseout',function(){ alert('鼠标移出'); }) btn7.addEventListener('mouseenter',function(){ alert('不带冒泡鼠标移入'); }) btn8.addEventListener('mouseleave',function(){ alert('不带冒泡鼠标移出'); }) </script>
-
鼠标移动 onmousemove
<body> <button id='btn9'>鼠标移出</button> </body> <script> btn9.addEventListener('mousemove',function(){ alert('鼠标移动'); }) </script>
-
滚轮滚动 onmousewheel
<body> <button id='btn10'>鼠标移出</button> </body> <script> btn9.addEventListener('mousewheel',function(e){ //为了不让页面跟着滚动,所以在这里处理一下默认事件 e.preventDefault(); console.log('滚轮在动'); }) </script>
十、获取鼠标事件对象位置
-
获取鼠标在屏幕中的坐标 screenX(Y)
-
获取鼠标在浏览器可视区域中的坐标 clientX(Y)
-
获取鼠标在文档中的坐标 pageX(Y)
-
获取鼠标在点击块中的坐标 offsetX(Y)
<!--获取坐标案例 样式自己设置--> <body> <!--在wrap中获取坐标--> <div id='wrap'></div> <div id='show'> 屏幕坐标screen(XY):<span id="show1"></span><br> 可视区域坐标client(XY):<span id="show2"></span><br> 文档中坐标page(XY):<span id='show3'></span><br> 目标对象中坐标offset(XY):<span id='show4'></span><br> </div> </body> <script> wrap.addEventListener('mousemove',function(e){ e = e || window.event; //获取鼠标在屏幕中的坐标 showXY(e.screenX,e.screenY,show1); //获取鼠标在浏览器可视区域中的坐标 showXY(e.clientX,e.clientY,show2); //获取鼠标在文档中的坐标 showXY(e.pageX,e.pageY,show3); //获取鼠标在wrap中的坐标 showXY(e.offsetX,e.offsetY.show4); }) function showXY(x,y,el){ el.innerHTML = `(${x},${y})` } </script>
十一、鼠标拖拽
拖拽步骤分解
- 绑定鼠标按下事件 onmousedown(绑定到盒子上)
- 获取鼠标坐标 坐标 = 鼠标针对点击盒子的偏移坐标 + 定位父级到文档
- this._x = e.offsetX + getBoxToDom(this.offsetParent,‘Left’);
- this._y = e.offsetY + getBoxToDom(this.offsetParent,‘Top’);
- 绑定鼠标移动事件 onmousemove (这里应该选择命名函数,方便后面删除移动事件)(绑定到document上)
- 绑定鼠标抬起事件 onmouseup (绑定到document上),删除移动事件
- 完善其余函数,移动事件函数(move),得到盒子到文档边缘距离函数(getBoxToDom).其中move函数中核心算法是设置拖拽盒子的left和top.(图解在下方 ,仅top方向)
- box.style.left = e.pageX - box._x + ‘px’;
- box.style.top = e.pageY - box._y + ‘px’;
示例
box.addEventListener('mousedown',function(e){
e = e || window.event;
//拿到鼠标的x 这里用this._x是为了在两个绑定事件中传参
this._x = e.offsetX + getBoxToDom(this.offsetParent,'Left');
//拿到鼠标的y
this._y = e.offsetY + getBoxToDom(this.offsetParent,'Top');
//绑定移动事件 采用命名函数,方便后期删除
document.addEventListener('mousemove',move);
})
document.addEventListener('mouseup',function(){
//删除move
this.removeEventListener('mousemove',move);
})
//工具函数01 移动函数
function move(e){
e = e || window.event;
box.style.left = e,pageX - box._x + 'px';
box.style.top = e.pageY - box._y + 'px';
}
//工具函数02 获取盒子到文档边缘距离
function getBoxToDom(el,fx){
if(el = null) return 0;
return el['offset' + fx] + el['client' + fx] + getBoxToDom(el.offsetParent,fx)
}
十二、拖拽函数封装
函数参数介绍
/* { el : 能够被拖拽的标签对象 dragging : function(el,evt), //监测拖拽过程 dragEnd : function(el,evt), //拖拽结束以后调用 dire : 'x' //控制拖拽方向,x方向只能水平,y代表只能垂直,不写就是两个方向 } */
drag.js代码 (采用标签对象来绑定鼠标事件)
function drag(options){
options = options || {};
//获取要实现拖拽的标签对象
var el = options.el || null;
//获取回调函数
var dragging = options.dragging;
//获取结束函数
var dragEnd = options.dragEnd;
//获取方向
var dire = options.dire;
//判断el是否存在
if(!el)return;
el.style.position = 'absolute';//避免el不是绝对定位
//绑定事件
el.onmousedown = function(e){
e = e || window.event;
//详情见 事件.md 笔记第十一条
this._x = e.offsetX + getBoxToDom(this.offsetParent,'Left');
this._y = e.offsetY + getBoxToDom(this.offsetParent,'Top');
//绑定移动事件
document.onmousemove = move;
}
document.onmouseup = function(e){
document.onmousemove = null;
//调用外部回调函数
if(typeof dragEnd == 'function'){
dragEnd(el,e);
}
}
//move移动过程中
function move(e){
e = e || window.event;
//判断如果
if(dire != 'y') el.style.left = e.pageX - el._x + 'px';
if(dire != 'x') el.style.top = e.pageY - el._y + 'px';
if(typeof dragging == 'function'){
dragging(el,e);
}
}
function getBoxToDom(el,fx){
if(el == null) return 0;
return el['offset' + fx] + el["client" + fx] + getBoxToDom(el.offsetParent,fx);
}
}
调用
<script src='指定路径'></script>
<script>
drag({
el : document.auerySelector('.box'),
dragging : function(el,evt){
//拖拽过程 用户自定义
},
dragEnd : function(el,evt){
//拖拽结束 用户自定义
},
dire : 'x'('y','zzzz') //'x'是只能横向,'y'是只能竖向,写其他的或者啥都不写就是两个方向都能拖动
})
</script>