绑定事件的两种方式/DOM事件的级别
我们在上一篇文章 DOM操作详解 中已经讲过事件的概念。这里讲一下注册事件的两种方式,我们以onclick事件为例。
DOM0的写法:onclick
1 2 3
| element.onclick = function () {
}
|
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <body> <button>点我</button> <script> var btn = document.getElementsByTagName("button")[0];
btn.onclick = function () { console.log("事件1"); }
btn.onclick = function () { console.log("事件2"); }
</script> </body>
|
点击按钮后,上方代码的打印结果:
我们可以看到,这种绑定事件的方式,会层叠掉之前的事件。
DOM2的写法:addEventListener
1 2 3
| element.addEventListener('click', function () {
}, false);
|
参数解释:
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <body> <button>按钮</button> <script> var btn = document.getElementsByTagName("button")[0];
btn.addEventListener("click", fn1); btn.addEventListener("click", fn2);
function fn1() { console.log("事件1"); }
function fn2() { console.log("事件2"); }
</script> </body>
|
点击按钮后,上方代码的打印结果:
我们可以看到,这种绑定事件的方式,不会层叠掉之前的事件。
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。比如鼠标操作时候,会添加鼠标位置的相关信息到事件对象中。
所有浏览器都支持event对象,但支持的方式不同。如下。
(1)普通浏览器支持 event。比如:
(2)ie 678 支持 window.event。
于是,我们可以采取一种兼容性的写法。如下:
1
| event = event || window.event;
|
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <script> document.onclick = function (event) { event = event || window.event;
console.log(event); console.log(event.timeStamp); console.log(event.bubbles); console.log(event.button); console.log(event.pageX); console.log(event.pageY); console.log(event.screenX); console.log(event.screenY); console.log(event.target); console.log(event.type); console.log(event.clientX); console.log(event.clientY); } </script> </body> </html>
|
event 属性
event 有很多属性,比如:
由于pageX 和 pageY的兼容性不好,我们可以这样做:
- 鼠标在页面的位置 = 被卷去的部分+可视区域部分。
Event举例
举例1:鼠标跟随
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> body { height: 5000px; }
img { position: absolute; padding: 10px 0; border: 1px solid #ccc; cursor: pointer; background-color: yellowgreen; } </style> </head> <body> <img src="" width="100" height="100"/>
<script>
var img = document.getElementsByTagName("img")[0]; var timer = null; var targetx = 0; var targety = 0; var leaderx = 0; var leadery = 0; document.onclick = function (event) { event = event || window.event; var pagey = event.pageY || scroll().top + event.clientY; var pagex = event.pageX || scroll().left + event.clientX;
targety = pagey - 30; targetx = pagex - 50;
clearInterval(timer); timer = setInterval(function () { leaderx = img.offsetLeft; var stepx = (targetx - leaderx) / 10; stepx = stepx > 0 ? Math.ceil(stepx) : Math.floor(stepx); leaderx = leaderx + stepx; img.style.left = leaderx + "px";
leadery = img.offsetTop; var stepy = (targety - leadery) / 10; stepy = stepy > 0 ? Math.ceil(stepy) : Math.floor(stepy); leadery = leadery + stepy; img.style.top = leadery + "px";
if (Math.abs(targety - img.offsetTop) <= Math.abs(stepy) && Math.abs(targetx - img.offsetLeft) <= Math.abs(stepx)) { img.style.top = targety + "px"; img.style.left = targetx + "px"; clearInterval(timer); } }, 30); }
</script>
</body> </html>
|
实现效果:
event应用举例:获取鼠标距离所在盒子的距离
关键点:
1
| 鼠标距离所在盒子的距离 = 鼠标在整个页面的位置 - 所在盒子在整个页面的位置
|
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .box { width: 300px; height: 200px; padding-top: 100px; background-color: pink; margin: 100px; text-align: center; font: 18px/30px "simsun"; cursor: pointer; } </style> </head> <body> <div class="box">
</div>
<script src="animate.js"></script> <script>
var div = document.getElementsByTagName("div")[0];
div.onmousemove = function (event) {
event = event || window.event; var pagex = event.pageX || scroll().left + event.clientX; var pagey = event.pageY || scroll().top + event.clientY; var targetx = pagex - div.offsetLeft; var targety = pagey - div.offsetTop; this.innerHTML = "鼠标在盒子中的X坐标为:" + targetx + "px;<br>鼠标在盒子中的Y坐标为:" + targety + "px;" }
</script> </body> </html>
|
实现效果:
举例:商品放大镜
代码实现:
(1)index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> * { margin: 0; padding: 0; }
.box { width: 350px; height: 350px; margin: 100px; border: 1px solid #ccc; position: relative; }
.big { width: 400px; height: 400px; position: absolute; top: 0; left: 360px; border: 1px solid #ccc; overflow: hidden; display: none; }
.mask { width: 175px; height: 175px; background: rgba(255, 255, 0, 0.4); position: absolute; top: 0; left: 0; cursor: move; display: none; }
.small { position: relative; }
img { vertical-align: top; } </style>
<script src="tools.js"></script> <script> window.onload = function () {
var box = document.getElementsByClassName("box")[0]; var small = box.firstElementChild || box.firstChild; var big = box.children[1]; var mask = small.children[1]; var bigImg = big.children[0];
small.onmouseenter = function () { show(mask); show(big); } small.onmouseleave = function () { hide(mask); hide(big); }
small.onmousemove = function (event) { event = event || window.event;
var pagex = event.pageX || scroll().left + event.clientX; var pagey = event.pageY || scroll().top + event.clientY;
var x = pagex - box.offsetLeft - mask.offsetWidth / 2; var y = pagey - box.offsetTop - mask.offsetHeight / 2;
if (x < 0) { x = 0; } if (x > small.offsetWidth - mask.offsetWidth) { x = small.offsetWidth - mask.offsetWidth; } if (y < 0) { y = 0; } if (y > small.offsetHeight - mask.offsetHeight) { y = small.offsetHeight - mask.offsetHeight; }
console.log(small.offsetHeight); mask.style.left = x + "px"; mask.style.top = y + "px";
var bili = bigImg.offsetWidth / small.offsetWidth;
var xx = bili * x; var yy = bili * y;
bigImg.style.marginTop = -yy + "px"; bigImg.style.marginLeft = -xx + "px"; } } </script> </head> <body> <div class="box"> <div class="small"> <img src="images/001.jpg" alt=""/> <div class="mask"></div> </div> <div class="big"> <img src="images/0001.jpg" alt=""/> </div> </div> </body> </html>
|
(2)tools.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
function show(ele) { ele.style.display = "block"; }
function hide(ele) { ele.style.display = "none"; }
function scroll() { if (window.pageYOffset != null) { return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { left: document.body.scrollLeft, top: document.body.scrollTop } }
|
效果演示:
小窗口拖拽案例
暂略。
DOM事件流
事件传播的三个阶段是:事件捕获、事件冒泡和目标。
-
事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标 target。(从祖先元素往子元素查找,DOM树结构)。在这个过程中,事件相应的监听函数是不会被触发的。
-
事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
-
事件冒泡阶段:事件从事件目标 target 开始,往上冒泡直到页面的最上一级标签。(从子元素到祖先元素冒泡)
如下图所示:
PS:这个概念类似于 Android 里的 touch 事件传递。
事件捕获
addEventListener可以捕获事件:
1 2 3
| box1.addEventListener("click", function () { alert("捕获 box3"); }, true);
|
上面的方法中,参数为true,代表事件在捕获阶段执行。
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| box3.addEventListener("click", function () { alert("捕获 child"); }, true);
box2.addEventListener("click", function () { alert("捕获 father"); }, true);
box1.addEventListener("click", function () { alert("捕获 grandfather"); }, true);
document.addEventListener("click", function () { alert("捕获 body"); }, true);
|
效果演示:
说明:捕获阶段,事件依次传递的顺序是:window --> document --> html–> body --> 父元素、子元素、目标元素。
PS1:第一个接收到事件的对象是 window(有人会说body,有人会说html,这都是错误的)。
PS2:JS中涉及到DOM对象时,有两个对象最常用:window、doucument。它们俩也是最先获取到事件的。
事件捕获阶段的完整写法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| window.addEventListener("click", function () { alert("捕获 window"); }, true);
document.addEventListener("click", function () { alert("捕获 document"); }, true);
document.documentElement.addEventListener("click", function () { alert("捕获 html"); }, true);
document.body.addEventListener("click", function () { alert("捕获 body"); }, true);
fatherBox.addEventListener("click", function () { alert("捕获 father"); }, true);
childBox.addEventListener("click", function () { alert("捕获 child"); }, true);
|
补充一个知识点:
在 js中:
二者不要混淆了。
事件冒泡
事件冒泡: 当一个元素上的事件被触发的时候(比如说鼠标点击了一个按钮),同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。
通俗来讲,冒泡指的是:子元素的事件被触发时,父盒子的同样的事件也会被触发。取消冒泡就是取消这种机制。
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| box3.onclick = function () { alert("child"); }
box2.onclick = function () { alert("father"); }
box1.onclick = function () { alert("grandfather"); }
document.onclick = function () { alert("body"); }
|
上图显示,当我点击儿子 box3的时候,它的父亲box2、box1、body都依次被触发了。即使我改变代码的顺序,也不会影响效果的顺序。
当然,上面的代码中,我们用 addEventListener 这种 DOM2的写法也是可以的,但是第三个参数要写 false,或者不写。
冒泡顺序:
一般的浏览器: (除IE6.0之外的浏览器)
- div -> body -> html -> document -> window
IE6.0:
- div -> body -> html -> document
不是所有的事件都能冒泡
以下事件不冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。意思是,事件不会往父元素那里传递。
我们检查一个元素是否会冒泡,可以通过事件的以下参数:
如果返回值为true,说明该事件会冒泡;反之则相反。
举例:
1 2 3 4 5 6
| box1.onclick = function (event) { alert("冒泡 child");
event = event || window.event; console.log(event.bubbles); }
|
阻止冒泡的方法
w3c的方法:(火狐、谷歌、IE11)
1
| event.stopPropagation();
|
IE10以下则是:
1
| event.cancelBubble = true
|
兼容代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| box3.onclick = function (event) {
alert("child");
event = event || window.event;
if (event && event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }
|
上方代码中,我们对box3进行了阻止冒泡,产生的效果是:事件不会继续传递到 father、grandfather、body了。
事件委托
事件委托,通俗地来讲,就是把一个元素响应事件(click、keydown…)的函数委托到另一个元素。
比如说有一个列表 ul,列表之中有大量的列表项 li:
1 2 3 4 5 6 7 8
| <ul id="parent-list"> <li id="li-1">Item 1</li> <li id="li-2">Item 2</li> <li id="li-3">Item 3</li> <li id="li-4">Item 4</li> <li id="li-5">Item 5</li> <li id="li-6">Item 6</li> </ul>
|
当我们的鼠标移到Li上的时候,需要获取此Li的相关信息并飘出悬浮窗以显示详细信息,或者当某个Li被点击的时候需要触发相应的处理事件。我们通常的写法,是为每个Li都绑定类似onMouseOver或者onClick之类的事件监听:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function addListeners4Li(liNode){ liNode.onclick = function clickHandler(){...}; liNode.onmouseover = function mouseOverHandler(){...} }
window.onload = function(){ var ulNode = document.getElementById("parent-list"); var liNodes = ulNode.getElementByTagName("Li"); for(var i=0, l = liNodes.length; i < l; i++){ addListeners4Li(liNodes[i]); } }
|
但是,上面的做法会消耗内存和性能。
因此,比较好的方法就是把这个点击事件绑定到他的父层,也就是 ul
上,然后在执行事件的时候再去匹配判断目标元素。如下:
1 2 3 4 5 6 7 8 9
| document.getElementById("parent-list").addEventListener("click", function (e) { if (e.target && e.target.nodeName.toUpperCase == "LI") { console.log("List item ", e.target.id, " was clicked!"); } }, false);
|
上方代码,为父节点注册click事件,当子节点被点击的时候,click事件会从子节点开始向上冒泡。父节点捕获到事件之后,开始执行方法体里的内容:通过判断 e.target 拿到了被点击的子节点li。从而可以获取到相应的信息,并作处理。
换而言之,事件是在父节点上注册的,参数为false,说明事件是在冒泡阶段触发(往上传递),那就只有父节点能拿到事件,子节点是不可能拿到事件的。
所以事件委托可以减少大量的内存消耗,提高效率。
事件委托的参考链接: