事件传播
DOM模型的事件先后沿着两个方向传播:在第一个阶段,也就是前面提到的事件捕获阶段,事件从最顶层的对象依次向下传播,因此先触发顶层对象的事件处理函数,然后依次向下,直到传播到事件所发生的最底层对象;接着进入第二个阶段,也就是前面提到的事件冒泡阶段,事件传播从底层一直上溯,直到最顶层的对象。
整个DOM模型的事件传播可以分成两个阶段:捕获阶段和冒泡阶段。捕获阶段的事件触发器总是比冒泡阶段的触发器先触发:在事件捕获阶段,顶层对象的事件处理器先被触发;而在事件冒泡阶段,则是底层对象的事件触发器先被触发。
DOM模型的事件传播示意图如下图所示。
为了阻止事件传播,DOM为Event对象提供了stopPropagation()方法,该方法的语法格式如下:
➢ event.stopPropagation():阻止event事件传播。如果我们在事件捕获阶段调用该方法阻止事件传播,则该事件根本不会进入事件传播阶段。
一旦调用该方法,该事件的传播就将被完全停止。值得注意的是DOM模型的事件传播顺序:在事件捕获阶段,事件从顶层对象传播到事件发生的目标对象;而在事件冒泡阶段,事件从事件发生的目标对象向上传播到顶层对象。
例子
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
<title> 阻止事件传播 </title>
</head>
<body>
友情链接:<br />
<!-- 目标超链接 -->
<a id="mylink" href="http://www.baidu.com">百度</a>
<div id="show"></div>
<script type="text/javascript">
// 事件捕获阶段的处理函数
var killClick1 = function(event)
{
// 取消默认事件的默认行为
event.preventDefault();
// 阻止事件传播
event.stopPropagation();
document.getElementById("show").innerHTML
+= '事件捕获阶段' + event.currentTarget + "<br />";
}
//事件冒泡阶段的处理函数
var killClick2 = function(event)
{
// 取消事件的默认行为
event.preventDefault();
// 阻止事件传播
event.stopPropagation();
document.getElementById("show").innerHTML
+= '事件冒泡阶段' + event.currentTarget + "<br />";
}
// // 在事件捕获阶段,分别为超链接对象、document对象绑定事件处理函数。
// document.addEventListener("click", killClick1, true);
// document.getElementById("mylink").addEventListener(
// "click", killClick1, true);
// 在事件冒泡阶段,分别为超链接对象、document对象绑定事件处理函数。
document.addEventListener("click", killClick2, false);
document.getElementById("mylink").addEventListener(
"click", killClick2, false);
</script>
</body>
</html>
根据上面的介绍,当事件传播处于捕获阶段时,从顶层对象向下传播,最先触发document对象的事件处理函数,该对象的事件处理函数阻止了事件传播,因而超链接对象上的事件处理函数不会触发,事件传播更不会进入事件冒泡阶段。
如果将事件捕获阶段的事件处理函数注释掉,即只为document对象和超链接对象的事件冒泡阶段绑定事件处理函数,此时超链接对象上的事件处理函数将先被触发,然后阻止事件传播,而document对象上的事件处理函数将不会被触发。
DOM实现了完备的事件处理机制,只要任何HTML元素在捕获阶段捕获了事件,并阻止了该事件的继续传播,事件就不会进入事件传播阶段,这对实现鼠标拖放等效果已经足够了。下面将实现一个跨浏览器的拖放效果,为了实现跨浏览器,程序在访问对象、属性或方法之前,总是先判断该浏览器是否支持该对象、属性或方法。HTML代码请参考IE拖拽模型中的代码。
例子
var drag = function(target, event)
{
// 定义开始拖动时的鼠标位置(相对window座标)
var startX = event.clientX;
var startY = event.clientY;
// 定义将要被拖动元素的位置(相对于document座标)
// 因为该target的position为absolutely,
// 所以我们认为它的座标系是基于document的
var origX = target.offsetLeft;
var origY = target.offsetTop;
// 因为后面根据event的clientX、clientY来获取鼠标位置时,
// 只能获取windows座标系的位置,所以需要计算window座标系
// 和document座标系的偏移。
// 计算windows座标系和document座标系之间的偏移
var deltaX = startX - origX;
var deltaY = startY - origY;
// 鼠标松开的事件处理器
var upHandler = function(evt)
{
// 对于IE事件模型,获取事件对象
if (!evt) evt = window.event;
// 取消被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)的事件处理器
if (document.removeEventListener)
{
// DOM事件模型
// 取消在事件捕获阶段的事件处理器
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}
else if (document.detachEvent)
{
target.detachEvent("onlosecapture", upHandler);
target.detachEvent("onmouseup", upHandler);
target.detachEvent("onmousemove", moveHandler);
target.releaseCapture( );
}
// 阻止事件传播
stopProp(evt);
}
// 阻止事件传播(该函数可以跨浏览器)
var stopProp = function(evt)
{
// DOM事件模型
if (evt.stopPropagation)
{
evt.stopPropagation( );
}
// IE事件模型
else
{
evt.cancelBubble = true;
}
}
// 为被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)注册事件处理器
if (document.addEventListener)
{
// DOM事件模型
// 在事件捕获阶段绑定事件处理器
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}
else if (document.attachEvent)
{
// IE事件模型
// 设置该元素直接捕获该事件
target.setCapture();
// 为该元素鼠标移动时绑定事件处理器
target.attachEvent("onmousemove", moveHandler);
// 为鼠标松开时绑定事件处理器
target.attachEvent("onmouseup", upHandler);
// 将失去捕获事件当成鼠标松开处理。
target.attachEvent("onlosecapture", upHandler);
}
// 阻止事件传播
stopProp(event);
// 取消事件默认行为
if (event.preventDefault)
{
// DOM事件模型
event.preventDefault( );
}
else
{
// IE事件模型
event.returnValue = false;
}
// 鼠标移动的事件处理器
function moveHandler(evt)
{
// 对于IE事件模型,获取事件对象
if (!evt) evt = window.event;
// 将被拖动元素的位置移动到当前鼠标位置。
// 先将window座标系位置转换成document座标系位置,再修改目标对象的CSS位置。
target.style.left = (evt.clientX - deltaX) + "px";
target.style.top = (evt.clientY - deltaY) + "px";
// 阻止事件传播
stopProp(evt);
}
}
转发事件
DOM提供了dispatchEvent方法用于事件的转发,该方法属于Node对象,因此DOM的每个Node元素都可调用该方法,从而将事件直接转发到本节点。该方法的语法格式如下:
➢ target.dispatchEvent(Event event):将event事件转发到target上。
与Internet Explorer事件模型里的重定向事件不同的是,dispatch()方法必须转发人工合成事件(Synthetic Event),不能直接转发系统创建的事件。
DOM为创建人工合成事件提供了如下方法。
➢ document.createEvent(String type):该方法创建一个事件对象,其中type参数用于指定事件类型,普通事件可使用Events,UI事件可使用UIEvents,鼠标事件可使用MouseEvents。
通过上面的方法得到一个事件后,可调用事件的如下方法来初始化。
➢ initEvent(String eventTypeArg,boolean canBubbleArg, boolean cancelableArg):用于初始化一个普通事件,第一个参数用于指定该事件类型,如click等;第二个参数用于指定该事件是否支持冒泡;第三个参数用于指定该事件是否有默认行为,且可通过preventDefault()方法取消该默认行为。
➢ initUIEvent(String typeArg, boolean canBubbleArg, boolean cancelableArg, Window viewArg, long detailArg):该方法的前3个参数的意义与上一个方法中的3个参数完全相同。后两个参数的意义与介绍UIEvent时的view、detail两个属性相同。
➢ initMouseEvent(String typeArg, boolean canBubbleArg, boolean cancelableArg, AbstractView viewArg, long detailArg, long screenXArg, long screenYArg, long clientXArg, long clientYArg, boolean ctrlKeyArg,boolean altKeyArg, boolean shiftKeyArg, boolean metaKeyArg, unsigned short buttonArg, Element relatedTargetArg):该方法的前5个参数的意义与上一个方法中的5个参数完全相同。后面参数的意义与介绍MouseEvent时的各属性相同。
例子
var drag = function(target, event)
{
// 定义开始拖动时的鼠标位置(相对window座标)
var startX = event.clientX;
var startY = event.clientY;
// 定义将要被拖动元素的位置(相对于document座标)
// 因为该target的position为absolutely,
// 所以我们认为它的座标系是基于document的
var origX = target.offsetLeft;
var origY = target.offsetTop;
// 因为后面根据event的clientX、clientY来获取鼠标位置时,
// 只能获取windows座标系的位置,所以需要计算window座标系
// 和document座标系的偏移。
// 计算windows座标系和document座标系之间的偏移
var deltaX = startX - origX;
var deltaY = startY - origY;
// 鼠标松开的事件处理器
var upHandler = function(evt)
{
// 对于IE事件模型,获取事件对象
if (!evt) evt = window.event;
// 取消被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)的事件处理器
if (document.removeEventListener)
{
// DOM事件模型
// 取消在事件捕获阶段的事件处理器
document.removeEventListener("mouseup", upHandler, true);
document.removeEventListener("mousemove", moveHandler, true);
}
else if (document.detachEvent)
{
target.detachEvent("onlosecapture", upHandler);
target.detachEvent("onmouseup", upHandler);
target.detachEvent("onmousemove", moveHandler);
target.releaseCapture( );
}
// 阻止事件传播
stopProp(evt);
}
// 阻止事件传播(该函数可以跨浏览器)
var stopProp = function(evt)
{
// DOM事件模型
if (evt.stopPropagation)
{
evt.stopPropagation( );
}
// IE事件模型
else
{
evt.cancelBubble = true;
}
}
// 为被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)注册事件处理器
if (document.addEventListener)
{
// DOM事件模型
// 在事件捕获阶段绑定事件处理器
document.addEventListener("mousemove", moveHandler, true);
document.addEventListener("mouseup", upHandler, true);
}
else if (document.attachEvent)
{
// IE事件模型
// 设置该元素直接捕获该事件
target.setCapture();
// 为该元素鼠标移动时绑定事件处理器
target.attachEvent("onmousemove", moveHandler);
// 为鼠标松开时绑定事件处理器
target.attachEvent("onmouseup", upHandler);
// 将失去捕获事件当成鼠标松开处理。
target.attachEvent("onlosecapture", upHandler);
}
// 阻止事件传播
stopProp(event);
// 取消事件默认行为
if (event.preventDefault)
{
// DOM事件模型
event.preventDefault( );
}
else
{
// IE事件模型
event.returnValue = false;
}
// 鼠标移动的事件处理器
function moveHandler(evt)
{
// 对于IE事件模型,获取事件对象
if (!evt) evt = window.event;
// 将被拖动元素的位置移动到当前鼠标位置。
// 先将window座标系位置转换成document座标系位置,再修改目标对象的CSS位置。
target.style.left = (evt.clientX - deltaX) + "px";
target.style.top = (evt.clientY - deltaY) + "px";
// 阻止事件传播
stopProp(evt);
}
}
取消事件的默认行为
DOM也提供了取消事件默认行为的方法,DOM中的事件对象都提供了preventDefault()方法,该方法不需要参数,只要执行了给定事件的preventDefault方法,该事件的默认行为就将失效。该方法的语法格式如下:
➢ event.preventDefault():取消event事件的默认行为。
preventDefault()方法虽然取消了事件的默认行为,但不会阻止事件传播,下面的代码为超链接和document在事件传播阶段绑定了事件处理函数。
例子
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
<title> 阻止默认行为 </title>
</head>
<body>
友情链接:<br />
<a id="mylink" href="http://www.baidu.com">百度</a>
<!-- 显示信息输出的div元素 -->
<div id="show"></div>
<script type="text/javascript">
var killClicks = function(event)
{
// 取消事件的默认行为
event.preventDefault();
document.getElementById("show").innerHTML
+= "事件捕获阶段:" + event.currentTarget + "<br>";
}
// 为document对象绑定事件处理函数
document.addEventListener("click", killClicks, true);
// 为超链接绑定事件处理函数
document.getElementById("mylink")
.addEventListener("click", killClicks, true);
</script>
</body>
</html>
注:本博客内容节选自李刚编著的疯狂HTML 5/CSS 3/JavaScript讲义 ,详细内容请参阅书籍。