window.addEventListener('load', function() {
var eventOrderSamps = document.getElementsByName('dom-event-order-sample');
for (var i = 0; i < eventOrderSamps.length; i++) {
var samp = eventOrderSamps[i];
samp.onclick = function() {
if (eventOrderTestText.length > 0) {
alert(eventOrderTestText);
eventOrderTestText = '';
[/codesyntax]
先来看元素覆盖的情况:
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-cover">
<span id="cover-1" name="sample-cover">
<span id="cover-1-1" name="sample-cover">
<span id="cover-1-1-1" name="sample-cover">
Click Me
</span>
</span>
</span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-cover'));
</script>
[/codesyntax]
Click Me
在点击之后,我们会看到元素的id按照冒泡的方式由内而外依次展示:
下边的例子,是对子元素使用了relative
的position
样式,使其不再覆盖其父元素,而覆盖了其它元素,看起来像是第二个元素的子元素:
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-relative-position">
<span id="relative-position-1" name="sample-relative-position">
<span id="relative-position-1-1" name="sample-relative-position"
style="background-color:#dd4b39; position:relative; left:110%;">
Click Me
</span>
</span>
<span id="relative-position-2" name="sample-relative-position" > </span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-relative-position'));
</script>
[/codesyntax]
Click Me
但是点击产生的效果,依然是传递给其真是的父结点,而被覆盖的元素则无法接受到红色部分传递点击事件过来。
对于这种现象,可以解释为逻辑树(Logical Tree)和视觉树(Visual Tree)上的不同,逻辑树规定了每个结点之间基本的关系,而视觉树则根据一些样式(Style)、变形(Transformation)来改变它的呈现效果。但是事件的激发顺序总是沿着逻辑树来传递。
这一点在Android的View动画(Animation)效果上就有体现:当一个按钮添加了Animation对象产生了位置的偏移,但是它的点击点依然在原来的地方。
从子元素传递给父元素的冒泡式很常用的事件传递方式,同样反过来从父元素把事件传递给子元素也是可行,而这就是捕获式:明明传给子元素的事件,却先被父元素截取先处理了。一图顶万言,来看看DOM Level 3 Events Specification上对事件流的描绘:
对于之前第一个例子,我们简单将事件以捕获的方式绑定,就会发现最后id输出顺利颠倒了:
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-capture">
<span id="capture-1" name="sample-capture">
<span id="capture-1-1" name="sample-capture">
<span id="capture-1-1-1" name="sample-capture">
Click Me
</span>
</span>
</span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-capture'), true);
</script>
[/codesyntax]
Click Me
捕获式只是说事件在接收者和其父结点之间相应的方式改变了,并不是事件激活时寻找激活点的顺序改变成从里往外。所以上边第二个例子,即使改成捕获式,点击红色区域也不会激活被覆盖元素的事件处理方法,而是先激发左侧蓝色元素,再激发红色元素的处理方法。
个人感觉对于捕获式,虽然只是传递顺序的改变,但是真正的实现这种方式并不那么简单。首先当事件方式的时候,我们总是先要找到响应该事件的最终端(这可以利用类似OpenGL的z buffering来检测)。在找到激活点元素之后,通过该元素找其父元素是简单的,应该每个结点只有一个父结点。通过冒泡方式,从端点到根点只要遍历一遍就可以把事件传播开去,还不需要任何额外的标记。而捕获式,我们可能需要先从端点到根点,并标记每一个路线,然后再从根点回到端点,这样我们就需要遍历两遍路径,并且还需要额外的标示。这可能就是为什么冒泡方式是各个事件处理的首先以及默认形式把。而且我也只在DOM事件机制中看到有捕获式的使用。应该各个浏览器引擎对此有更好的实现方式。
在上图中,我们也看到了整个事件传递过程有三个阶段:捕获阶段,目标阶段,冒泡阶段。从顺序上讲,捕获式绑定的事件处理方法要优先于冒泡式绑定的事件处理方法,不过这只是针对元素作为父结点的时候,当元素是激活目标时,各种方式绑定的事件处理方法被执行顺序不确定的。
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-combine">
<span id="combine-1" name="sample-combine">
<span id="combine-1-1" name="sample-combine">
<span id="combine-1-1-1" name="sample-combine">
Click Me
</span>
</span>
</span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-combine'), false);
bindEventFor(document.getElementsByName('sample-combine'), true);
</script>
[/codesyntax]
Click Me
在上边的例子里,对于每个元素都同时以冒泡和捕获方式绑定了事件处理方法,并且先为冒泡方式进行绑定,再以捕获方式绑定。当点击发生后,我们可以看到输出结果:
从结果上可以看出,父元素的事件处理方法都按捕获和冒泡都有序地正确执行,而在目标阶段,目标元素自身的冒泡方式却先于捕获方式执行。可以认为这是按照绑定顺序进行的,但是这不足以保证。
隐藏的元素
接下来,再看一下在页面上看不见的元素对事件的“态度”。如何让元素看不见呢?最常用的两个样式属性就是 ;
和;
了。前者会保留元素原来的位置,后者不会。对于后者,我们连点击的位置都没有,所以可以说直接忽略事件。那么前者呢?点击它原来的位置是否还有效?
修改前边第二个例子,给第一个蓝色元素添加一个;
的样式:
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-relative-hidden">
<span id="relative-hidden-1" name="sample-relative-hidden" style=";">
<span id="relative-hidden-1-1" name="sample-relative-hidden"
style="background-color:#dd4b39; position:relative; left:110%;">
Click Me
</span>
</span>
<span id="relative-hidden-2" name="sample-relative-hidden" ></span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-relative-hidden'));
</script>
[/codesyntax]
Click Me
之前我们点击第二个蓝色元素的时候,总是被红色部分拦截,而现在则可以正常点击,而第一个元素虽然还有占位,但是不会响应点击事件了。
除了使用上边两个方法让元素在页面上被隐藏,其实还有另一种方法可以它消失,那就是设置opacity
值为0让元素变成透明。看起来的效果跟visible:hidden;
是一样的。这里要注意的是opacity:0;
和颜色的alpha
值为0是不同的,前者会让其自身和子元素都变成透明不可见,后者只是说设置的颜色是透明的,不会影响到其子元素的内容设置和呈现。
[codesyntax lang=”xml”]
<samp name="dom-event-order-sample" id="sample-relative-transparent">
<span id="relative-transparent-1" name="sample-relative-transparent"
style="opacity:0; filter:alpha(opacity=0); ">
<span id="relative-transparent-1-1" name="sample-relative-transparent"
style="background-color:#dd4b39; position:relative; left:110%;">
Click Me
</span>
</span>
<span id="relative-transparent-2" name="sample-relative-transparent" >
</span>
</samp>
[/codesyntax][codesyntax lang=”javascript”]
<script type="text/javascript">
bindEventFor(document.getElementsByName('sample-relative-transparent'));
</script>
[/codesyntax]
Click Me
此时我们再点击蓝色区域,我们依然会得到与第二个例子一样的结果:
这相当于是说opacity:0;
只是视觉不可见,而逻辑依然可以
EventTarget – Document Object Model (DOM) | MDN
Document Object Model (DOM) Level 3 Events Specification – W3C
WPF Tutorial | Logical- and Visual Tree
Javascript – Event order
Bubbling and capturing | JavaScript Tutorial
What’s the difference between event.stopPropagation and event.preventDefault? – Stack Overflow