事件绑定
- 交互体验的核心功能
- dom.on### = function(){}
- 这种绑定兼容性特别好
- this 指向 dom 元素自己
- 缺点:一个事件只能绑定一个函数,写两个的话 后一个会覆盖前一个
mydiv.onclick = function(){console.log("a");}
mydiv.onclick = function(){console.log("b");}
只能输出 b, 因为它本质上还是属性赋值 - 基本等同于写在元素行间上
<div οnclick="console.log("b");">
- dom.addEventListener(事件类型,处理函数,false) 事件监听机制
- div.addEventListener('click', function(){}, false); //注意 这里是 click 不是 onclick
- this 指向 dom 元素自己
- IE9 以下不兼容
- 可以给一个对象的一个事件绑定多个处理函数, 并且按照绑定的顺序来执行
div.addEventListener('click', function(){console.log("a");}, false);
div.addEventListener('click', function(){console.log("b");}, false);
a, b 都能打印出来 - div.addEventListener('click', test, false);
div.addEventListener('click', test, false);
var test = function(){console.log("a");}
这个算绑定一次,只会打印出一个 a - 事件监听最后的参数变成 true, 就会立刻变成事件捕获模型, false 的时候是冒泡
- dom.attachEvent('on' + 事件类型,处理函数)
- dom.attachEvent('onclick', function(){});
- IE 特有的方法,谷歌火狐啥的都不行
- this 指向 window,这个其实相当于一个 IE 浏览器的一个 bug 了,我们肯定是想让 this 指向 dom 元素自己,我们就自己处理
div.attachEvent('onclick', function(){test().call(div);}, false);
var test = function(){console.log(this);}//这里的 this 就指向自己了 - 和 addEventListener 差不多 但是div.attachEvent('onclick', test, false);
div.attachEvent('onclick', test, false);
var test = function(){console.log("a");}
这个算绑定两次,只会打印出两个 a
-
封装兼容性的事件绑定,兼容性最好的时间绑定 Element.prototype.addEvent = function(type, handle){ if(this.addEventListener){ this.addEventListener(type, handle, false); }else if(this.attachEvent){ this.attachEvent('on' + type, function(){ handle.call(this); //处理 IE this 指向 window 的 bug }); }else{ this['on'+type] = handle; } } var div = document.getElementsByClassName("carousel")[0]; div.addEvent("click", function() { console.log(this); });
- onclick 单击事件
var mydiv = document.getElementsByTagName("div")[0];
mydiv.onclick = function(){} - 键盘事件
- onkeydown/onkeyup 用户按下/抬起键盘任意按键
document.onkeydown = function(e){
var event = e || window.event; //兼容 IE
console.log(event); //这里查看上下左右的键值
if(e.code == "ArrowLeft") alert("a");
} - onkeypress
- keydown 可以检测所有键盘按键(除了 fn), keypress 只可以检测字符类按键(ascii 码表里有的字符)
- keypress 有 e.charCode 属性值, 它可以判断你按的是按钮 a 还是 A 或者你按的是 b
document.onkeypress = function(e){console.log( String.fromCharCode(e.charCode));} //把 unicode 编码转换成字符 - e.which 可以检测你按了哪个按键 一共 108个数,但是检测不了你按的是 a 还是 A
- 触发顺序 down, press, up
连续按的时候不抬起会连续触发 down, press 不是只触发一次
这样魔兽世界就能开始连续跑了
- onkeydown/onkeyup 用户按下/抬起键盘任意按键
- 鼠标事件
- onmouseenter(onmouseover)/ onmouseleave(onmouseout) 鼠标移入,鼠标移出 (out over 是老版本的)
画板,刮刮乐 Demo<style type="text/css"> li{ box-sizing: border-box; /*让元素加上 border 之后仍然等于 width*/ float: left; width: 10px; height: 10px; border:1px solid black; } ul#aaa{ list-style: none; width: 100; height: 100px; } </style> <ul id="aaa"> <li img-date="0"> img-date 自定义属性</li> <li img-date="0"></li> <li img-date="0"></li> <li img-date="0"></li> <li img-date="0"></li> </ul> var myUl = document.getElementById("aaa"); myUl.onmouseover = function(e) { var event = e || window.event; //兼容 IE var target = event.target || event.srcElement; //事件源绑定,就相当于操作 ul 里的每个 li 所以这是一个鼠标滑过 li 每个单独的 target.style.backgroundColor = "rgb(255, 255," + target.getAttribute('img-date') + ")"; //正常应该是 background-color, 为啥用驼峰写法? 因为 JS 不允许有带 - 的属性名 mydiv.className = "aa"; var now_date = parseInt(target.getAttribute('img-date')) + 20; target.setAttribute('img-date', now_date); } 、 点击第一个 li 输出 0, 第二个输出 1... var liArr = document.getElementsByTagName("li"), len = liArr.length; for (var i = 0; i <= len, i++){ (function(i){ liArr[i].onclick = function(){console.log(i);} }(i)); //注意这里要用立即执行函数来锁定 i 的值,在立即执行函数里 i 是形参,立即执行函数里有自己的 AO //如果事件在循环里,但是事件里用不到 i 那么这个类似闭包其实也不要紧,不用立即执行函数也行 } 也可以用事件委托来处理
- onmousemove: 鼠标在元素内移动
利用 onmouseenter/onmouseover, onmouseleave/onmouseout 和 onmousemove 写一个拖拽小方块儿的功能 window.onload = function() { var myDiv = document.getElementsByTagName("div")[0]; drag(myDiv); function drag(elem) { var disX, disY; Element.prototype.addEvent = function(type, handle) { //封装添加事件函数 if (this.addEventListener) { this.addEventListener(type, handle, false); } else if (this.attachEvent) { this.attachEvent('on' + type, function() { handle.call(this); //处理 IE this 指向 window 的 bug }); } else { this['on' + type] = handle; } } Document.prototype.addEvent = Element.prototype.addEvent; elem.addEvent("mousedown", function(e) { //鼠标点击 div 之后才绑定 onmousemove onmouseup 事件 var event = e || window.event; disX = event.pageX - parseInt(this.style.left); disY = event.pageY - parseInt(this.style.top); //鼠标点击的点 - div 距离左边的距离,等于鼠标相对 div 内部的距离,这样才能让鼠标拖拽的时候始终保持在开始点击的位置 document.addEvent("mousemove", function(e) { //鼠标点拖 var event = e || window.event; elem.style.left = event.pageX - disX + "px"; elem.style.top = event.pageY - disY + "px"; }); document.addEvent("mouseup", function() { //鼠标放手 document.onmousemove = null; }); stopBubble(event); //阻止冒泡 cancelHander(event); //阻止默认事件 function stopBubble(event) { if(event.stopPropagation){event.stopPropagation();} else {event.cancleBubble = true;} } 封装一个函数,阻止默认事件 function cancelHander(e){ if(e.preventDefault) {e.preventDefault();} else (e.returnValue = false;) } }); } } Tips : div 设置了 position 才有 left,top 属性, position: absolution; 区分拖拽和点击 Tips: 触发顺序 down, up, click 和绑定顺序没有关系 var firstTime = 0, lastTime = 0; key = false; button.addEvent("click", function() { if(key){ ...执行点击事件 key = false; } }); button.addEvent("onmousedown", function() { firstTime = newDate().getTime(); }); button.addEvent("onmouseup", function() { lastTime = newDate().getTime(); if(lastTime - firstTime < 300){ //时间短了算点击 key = true; } //时间长了算拖拽 });
- onmousedown/onmouseup == click : 鼠标按下/鼠标抬起
触发顺序 down, up, click 和绑定顺序没有关系- e.button; 区分鼠标左右键,只有 "onmousedown/onmouseup" 事件能区分鼠标按键,其他都不可能
0 1 2 左中右
DOM3 标准规定, click 事件只能监听左键。 - 移动端 onmousedown,onmousemove,onmouseup 就不好用了,我们要写另外的事件 touchstart touchmove, touchend
- e.button; 区分鼠标左右键,只有 "onmousedown/onmouseup" 事件能区分鼠标按键,其他都不可能
- contextmenu //右键弹出菜单事件
- onmouseenter(onmouseover)/ onmouseleave(onmouseout) 鼠标移入,鼠标移出 (out over 是老版本的)
- 文本类操作事件
- oninput : input 控件的事件,但凡input的文本有变化,无论增加还是删除,都会触发该事件
- onchange HTML 元素改变并且对比 input 框获得焦点和失去焦点的值不一致才触发
- onblur 失去焦点
- onfocus 得到焦点
- <input type="text" style="color:#999;" value="请输入用户名" οnfοcus="if(this.value=='请输入用户名'){this.value=''; this.style.color='#424242';}" οnblur="if(this.value==''){this.value = '请输入用户名'; this.style.color='#999';}">
- 窗体类操作事件
- scroll 滚动条滚动,事件触发
window.onscroll = function(){ getScrollOffset();//获得滚动条滚动的位置}
fix 条位置: 一个小块儿 当前的 top 位置,加上滚动条滚动的位置, 就相当于没有动, 广告条 - load
- window.onload = function(){} 浏览器已完成页面的加载
不要用,这个方法是最慢的, 效率最低
在页面加载的时候,html 和 css 是并行一起被解析的,在解析的时候分别生成 domTree 和 cssTree 它们加合并在一起汇成了一个 renderTree (渲染树) 然后再汇成一个页面 我们把 JS 写在下边,就相当于在生成 domTree 的时候页面刚刚解析完(而不是下载完)的时候就执行 JS
比如说 image, 图片 size 很大,解析完的意思是: 认出了这个是一个 image 标签后然后马上挂到树上去,而开启另一个新的线程,异步同时下载这个图片。
window.onload: 要等整个页面全部解析完, domtree, css tree, render tree 就绪, 所有文档,信息,图片等全部下载完,整个页面全部就绪的时候执行。
文档解析完成触发的方法:
document.addEventListener('DOMContentLoaded',function(){...js 写法... },false);
$(document).ready(function(){ ... 这个是jQuery 写法...})
当然最快的方法还是直接把 js 写在页面下面
- scroll 滚动条滚动,事件触发
- submit, reset, select 等事件
事件解除
- dom.onclick = null; 解除单击事件
实际应用,某些事件只想执行一次
mydiv.onclick = function(){... this.onclick = null;} - dom.removeEventlistener(事件类型,处理函数,false);解除事件监听
div.addEventListener('click', function(){}, false); //这种绑定匿名函数,永远清除不了
div.addEventListener('click', test, false); //这样通过函数名来清除 - dom.detachEvent('on' + 事件类型,处理函数)
绑定匿名函数,则无法解除
事件处理模型
- 事件冒泡
- 结构上(非视觉上),嵌套关系的元素,会存在事件冒泡的功能,既同一事件,自子元素冒泡向父元素。(从代码结构看起来是自底向上)
- focus, blur, change, submit, reset, select 等事件不冒泡
- 事件捕获
- 结构上(非视觉上),嵌套关系的元素,会存在事件捕获的功能,既同一事件,自父元素获取子元素(事件源元素)。 (自顶向下)
- IE 上没有事件捕获,Chrome 是唯一一直有的浏览器,最新版本的火狐也可以
- dom.addEventListener(事件类型,处理函数,true)
事件监听最后的参数变成 true, 就会立刻变成事件捕获模型, false 的时候是冒泡 - 它和冒泡事件执行顺序正好相反。如点击事件: 先执行父元素的点击事件,然后一层一层往子元素里执行
- IE 上有一个特殊的捕获事件的方法 dom.setCapture(); 它能把这个页面上所有的其他事件,都捕获到自己身上,比如说随便点击页面其他地方,都算点了自己。 这个方法可以解决鼠标拖拽 div 太快的时候飞出去的问题,但是,我们不用它。了解即可。
它对应的释放方法是 dom.releaseCapture();
- 触发顺序: 先捕获,后冒泡。
同一个对象的同一个事件类型,上面绑定了两个事件处理函数,一个是捕获一个是冒泡的执行顺序<div class="wraper"> <div class="content"> <div class="box"></div> </div> </div> var wraper=document.getElementsByClassName('wraper')[0]; var content=document.getElementsByClassName('content')[0]; var box=document.getElementsByClassName('box')[0]; wraper.addEventListener('click',function(){ console.log("wraperBulle"); },false); content.addEventListener('click',function(){ console.log("contentBubble"); },false); box.addEventListener('click',function(){ console.log("boxBubble"); },false); //事件捕获 wraper.addEventListener('click',function(){ console.log("wraper"); },true); content.addEventListener('click',function(){ console.log("content"); },true); box.addEventListener('click',function(){ console.log("box"); },true); 运行结果 wraper content boxBubble //这里不是先执行捕获么? 为何先执行了冒泡? box contentBubble wraperBulle //点击 box 是这样的: 它的执行是这样的,先捕获 wraper, 然后捕获 content, 然后是 box 区域的事件执行, 然后冒泡到 content 再冒泡到 wraper. //这里 box 区域是两个事件执行, 事件执行的顺序要符合:谁先绑定,谁先执行。
取消冒泡事件和阻止默认事件
-
取消冒泡
- W3C event.stopPropagation(); 但不支持 ie9 以下版本
- IE 独有 event.cancleBubble = true; (目前谷歌也能实现了)
-
封装阻止冒泡的函数,兼容 function stopBubble(event) { if(event.stopPropagation){event.stopPropagation();} else {event.cancleBubble = true;} } div.onclick = function(e){ var event = e || window.event; //兼容 IE stopPropagation(e); } 在每一个事件处理函数中,可以写一个形参:事件对象 e, 写多了也没有用,我们不用传东西,系统会自动帮我们传
-
阻止默认事件
- 默认事件: 表单提交,a 标签跳转,鼠标右键出菜单等...
- return false; 以对象的方式注册的事件才生效, 也就是它值适用于 on## = function(){} 产生的事件,不适用于 addEventListener 或者 attachEvent 产生的事件
取消鼠标右键出菜单事件
document.oncontextmenu = function(){console.log("a");} //此时鼠标右键既出菜单,又打印 a
document.oncontextmenu = function(){console.log("a"); return false;} //此时右键不出菜单,只打印 a - e.preventDefault(); W3C 标准
- e.returnValue = false; IE 浏览器使用
-
封装一个函数,阻止默认事件 function cancelHander(e){ if(e.preventDefault) {e.preventDefault();} else (e.returnValue = false;) } document.oncontextmenu = function(e){ console.log("a"); cancelHander(e);// 取消右键弹出菜单 }
- 我们经常拿 a 标签当做按钮来用,我们如何取消 a 标签的跳转或者刷新页面的功能
方法 一: <a href="http://www.ibm.com/..." οnclick="aa();"></a>
function aa(){
...
return false;
}
方法二:
a 标签 href= 里可以写协议限定符 javascrip: 这样后面的就是 js 代码
(比如 <a href="javascrip:alert('a')">),
在标签的行间里写 void 相当于直接写返回值, void(0)/void(false) 都相当于 return false;
<a href="javascrip:void(0)" οnclick="aa();"></a>
事件源对象 e
- IE 浏览器没有 e 对象
div.onclick = function(e){
var event = e || window.event; //兼容 IE
} - e.clientX/PageX; e.clientY/PageY; 鼠标点击事件的时候所点击的坐标点
- var target = event.target || event.srcElement; //事件源绑定兼容性写法
父子元素, 点击父元素(没有碰到子元素),事件源才是父元素, 如果点击的是子元素,是通过冒泡或者事件捕获到了父元素的,那么事件源是子元素- event.target 火狐只有这个
- event.srcElement IE 只有这个
- Chrome 都有
- e.button; 区分鼠标左右键,只有 "onmousedown/onmouseup" 事件能区分鼠标按键,其他都不可能
0 1 2 左中右
事件委托
- 事件委托:利用事件冒泡,和事件源对象进行处理
让自己的所触发的事件,让他的父元素代替执行! - 实际应用:就是一个 ul 有 一百万个 li,点击li 打印li 里的内容。 并且之后动态添加了 li 会自动被添加事件
myUl.onclick = function(e) { var event = e || window.event; var target = event.target || event.srcElement; console.log(target.innerText); }
- 优点
- 性能: 不需要循环所有子元素一个一个绑定事件,效率高
- 灵活: 当动态添加子元素时,不需要重新绑定事件,可扩展性好