JS 13.4.5-13.4.9 复合事件&变动事件&HTML5事件&设备事件&触摸与手势事件

复合事件

复合事件(composition event)是DOM3级事件中新添加的一类事件,用于处理IME的输入序列;

IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符;

例如,使用拉丁文键盘的用户通过IME照样能输入日文字符;

IME 通常需要同时按住多个键,但最终只输入一个字符;

复合事件就是针对检测和处理这种输入而设计的;有以下三种复合事件;

compositionstart,compositionupdate,compositionend;

变动事件

DOM2级的变动(mutation)事件能在DOM中的某一部分发生变化时给出提示;

变动事件是为XML或HTML DOM设计的,并不特定于某种语言;

DOM2级定义了如下变动事件;

1.DOMSubtreeModified:在DOM结构中发生任何变化时触发;这个事件在其他任何事件触发后都会触发;

2.DOMNodeInserted:在一个节点作为子节点被插入到另一个节点中时触发;

3.DOMNodeRemoved:在节点从其父节点中被移除时触发;

4.DOMNodeInsertedIntoDocument:

在一个节点被直接插入文档或通过子树间接插入文档之后触发;这个事件在DOMNodeInserted之后触发;

5.DOMNodeRemovedFromDocument:

在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发;这个事件在DOMNodeRemoved之后触发;

6.DOMAttrModified:在特性被修改之后触发;

7.DOMCharacterDataModified:在文本节点的值发生变化时触发;

能力检测;

var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");

删除节点

在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件;

这个事件的目标(event.target)是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用;

在这个事件触发时,节点尚未从其父节点删除;

紧随其后触发的是DOMSubtreeModified事件;这个事件的目标是被移除节点的父节点;

此时的event对象也不会提供与事件相关的其他信息;

<! DOCTYPE html>
<html>
<head>
<title>Node Removal Events Example</title>
</head>
<body>
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</body>
</html>

假设要移除<ul>元素;此时,就会依次触发以下事件; (1) 在<ul>元素上触发DOMNodeRemoved事件;relatedNode属性等于document.body; (2) 在<ul>元素上触发DOMNodeRemovedFromDocument事件; (3) 在身为<ul>元素子节点的每个<li>元素及文本节点上触发DOMNodeRemovedFromDocument事件; (4) 在document.body上触发DOMSubtreeModified事件,因为<ul>元素是document.body的直接子元素;

验证顺序;

EventUtil.addHandler(window, "load", function (event) {
    var list = document.getElementById("myList");
    EventUtil.addHandler(document, "DOMSubtreeModified", function (event) {
        alert(event.type);
        alert(event.target);
    });
    EventUtil.addHandler(document, "DOMNodeRemoved", function (event) {
        alert(event.type);
        alert(event.target);
        alert(event.relatedNode);
    });
    EventUtil.addHandler(list.firstChild, "DOMNodeRemovedFromDocument", function (event) {
        alert(event.type);
        alert(event.target);
    });
    list.parentNode.removeChild(list);
});

DOMNodeRemovedFromDocument不会冒泡;

DOMNodeRemoved事件冒泡;

插入节点

使用appendChild(),replaceChild()或insertBefore()向DOM中插入节点时,

首先会触发DOMNodeInserted事件;

这个事件的目标是被插入的节点,而event.relatedNode属性中包含一个对父节点的引用;

在这个事件触发时,节点已经被插入到了新的父节点中;

紧接着,会在新插入的节点上面触发DOMNodeInsertedIntoDocument事件;

DOMSubtreeModified,触发于新插入节点的父节点;

DOMNodeInserted,DOMSubtreeModified是冒泡的;

DOMNodeInsertedIntoDocument是不冒泡的;

HTML5事件

DOM规范没有涵盖所有浏览器支持的所有事件;

很多浏览器出于不同的目的——满足用户需求或解决特殊问题,还实现了一些自定义的事件;

HTML5详尽列出了浏览器应该支持的所有事件,本文只描述被实现了的事件;

contextmenu事件

在所有浏览器中都可以取消这个事件:在兼容DOM的浏览器中,使用event.preventDefalut();

在IE中,将event.returnValue的值设置为false;

<!DOCTYPE html>
<html>
<head>
<title>ContextMenu Event Example</title>
</head>
<body>
<div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
Click anywhere else to get the default context menu.</div>
<ul id="myMenu" style="position:absolute;visibility:hidden;background-color:
silver">
<li><a href="http://www.nczonline.net">Nicholas’ site</a></li>
<li><a href="http://www.wrox.com">Wrox site</a></li>
<li><a href="http://www.yahoo.com">Yahoo!</a></li>
</ul>
</body>
</html>

隐藏默认菜单,使用自定义菜单;

EventUtil.addHandler(window, "load", function (event) {
    var div = document.getElementById("myDiv");
    EventUtil.addHandler(div, "contextmenu", function (event) {
        event = EventUtil.getEvent(event);
        EventUtil.preventDefault(event);
        var menu = document.getElementById("myMenu");
        menu.style.left = event.clientX + "px";
        menu.style.top = event.clientY + "px";
        menu.style.visibility = "visible";
    });
    EventUtil.addHandler(document, "click", function (event) {
        document.getElementById("myMenu").style.visibility = "hidden";
    });
});

beforeunload事件

为了让开发人员有可能在页面卸载前阻止这一操作;

这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面;

但是,不能彻底取消这个事件,因为那就相当于让用户无法离开当前页面了;

EventUtil.addHandler(window, "beforeunload", function (event) {
    event = EventUtil.getEvent(event);
    var message = "I'm really going to miss you if you go.";
    event.returnValue = message;
    return message;
});

主要用处就是提醒用户是否真的想离开当前页面;

DOMContentLoaded事件

window的load事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折;

而DOMContentLoaded事件则在形成完整的DOM树之后就会触发,

不理会图像,JavaScript文件,CSS文件或其他资源是否已经下载完毕;

不是所有浏览器都支持;

readystatechange事件

这个事件的目的是提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料;

支持readystatechange事件的每个对象都有一个readyState属性,可能包含下列5个值中的一个;

uninitialized,loading,loaded,interactive,complete;

这些状态看起来很直观,但并非所有对象都会经历readyState的这几个阶段;

换句话说,如果某个阶段不适用某个对象,则该对象完全可能跳过该阶段;

并没有规定哪个阶段适用于哪个对象;

显然,这意味着readystatechange事件经常会少于4次,而readyState属性的值也不总是连续的;

pageshow和pagehide事件

Firefox和Opera有一个特性,名叫”往返缓存”(back-forward cache,或bfcache),

可以在用户使用浏览器的”后退”和”前进”按钮时加快页面的转换速度;

这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;

实际上是将整个页面都保存在了内存里;

如果页面位于bfcache中,那么再次打开该页面时就不会触发load事件;

为了说明bfcache的行为;Firefox提供了一些新事件;

pageshow:这个事件在页面显示时触发,无论该页面是否来自bfcache;

在重新加载的页面中,pageshow会在load事件触发后触发;

而对于bfcache中的页面,pageshow会在页面状态完全恢复的那一刻触发;

虽然这个事件的目标是document,但必须将其事件处理程序添加到window;

除了通常的属性之外,pageshow事件的event对象还包含一个名为persisted的布尔值属性;

如果页面被保存在了bfcache中,则这个属性的值为true;否则,这个属性的值为false;

相应的,pagehide在浏览器卸载页面的时候触发,而且是在unload事件之前触发;

这个事件的event对象也包含persisted属性;

hashchange事件

HTML5新增了hashchange事件,以便在URL的参数列表(及URL 中”#”号后面的所有字符串)发生变化时通知开发人员;

之所以新增这个事件,是因为在Ajax应用中,开发人员经常要利用URL参数列表来保存状态或导航信息;

检测;

var isSupported = ("onhashchange" in window) && (document.documentMode ===
    undefined || document.documentMode > 7);

设备事件

智能手机和平板电脑;

orientationchange事件

用户何时将设备由横向查看模式切换为纵向查看模式;

移动Safari的window.orientation属性中可能包含3个值:

0表示肖像模式,

90表示向左旋转的横向模式(“主屏幕”按钮在右侧),

-90表示向右旋转的横向模式(“主屏幕”按钮在左侧);

所有iOS设备都支持orientationchange事件和window.orientation属性;

MozOrientation事件

当设备的加速计检测到设备方向改变时,就会触发这个事件;

与iOS中的orientationchange事件不同,该事件只能提供一个平面的方向变化;

EventUtil.addHandler(window, "MozOrientation", function(event){
//响应事件
});

event对象包含三个属性:x,y和z;

这几个属性的值都介于1 到-1 之间,表示不同坐标轴上的方向;

在静止状态下,x值为0,y值为0,z值为1(表示设备处于竖直状态);

如果设备向右倾斜,x值会减小;反之,向左倾斜,x值会增大;

类似地,如果设备向远离用户的方向倾斜,y值会减小,向接近用户的方向倾斜,y值会增大;

z轴检测垂直加速度度,1表示静止不动,在设备移动时值会减小;失重状态下值为0;

deviceorientation事件

deviceorientation事件的意图是告诉开发人员设备在空间中朝向哪儿,而不是如何移动;

触发deviceorientation事件时,事件对象中包含着每个轴相对于设备静止状态下发生变化的信息;

事件对象包含以下5个属性;

alpha,beta,gamma,absolute,compassCalibrated;

devicemotion事件

告诉开发人员设备什么时候移动;

包含属性:acceleration,accelerationIncludingGravity,interval,rotationRate;

如果以上值读不到,为null;

EventUtil.addHandler(window, "devicemotion", function(event){
    var output = document.getElementById("output");
    if (event.rotationRate !== null){
        output.innerHTML += "Alpha=" + event.rotationRate.alpha + ", Beta=" +
        event.rotationRate.beta + ", Gamma=" +
        event.rotationRate.gamma;
    }
});

触摸与手势事件

只针对触摸设备;

触摸事件

touchstart,touchmove,touchend,touchcancel;

这几个事件都会冒泡,也都可以取消;

虽然这些触摸事件没有在DOM规范中定义,但它们却是以兼容DOM的方式实现的;

因此,每个触摸事件的event对象都提供了在鼠标事件中常见的属性:

bubbles,cancelable,view,clientX,clientY,screenX,screenY,detail,altKey,shiftKey,ctrlKey,metaKey;

除此之外,还有三个跟踪触摸的属性:touches,targetTouches,changeTouches;

每个Touch包含的属性:clientX,clientY,identifier,pageX,pageY,screenX,screenY,target;

function handleTouchEvent(event) {
//只跟踪一次触摸
    if (event.touches.length == 1) {
        var output = document.getElementById("output");
        switch (event.type) {
            case "touchstart":
                output.innerHTML = "Touch started (" + event.touches[0].clientX +
                    "," + event.touches[0].clientY + ")";
                break;
            case "touchend":
                output.innerHTML += "<br>Touch ended (" +
                    event.changedTouches[0].clientX + "," +
                    event.changedTouches[0].clientY + ")";
                break;
            case "touchmove":
                event.preventDefault(); //阻止滚动
                output.innerHTML += "<br>Touch moved (" +
                    event.changedTouches[0].clientX + "," +
                    event.changedTouches[0].clientY + ")";
                break;
        }
    }
}

EventUtil.addHandler(document, "touchstart", handleTouchEvent);
EventUtil.addHandler(document, "touchend", handleTouchEvent);
EventUtil.addHandler(document, "touchmove", handleTouchEvent);

事件发生顺序如下:

(1) touchstart

(2) mouseover

(3) mousemove(一次)

(4) mousedown

(5) mouseup

(6) click

(7) touchend

手势事件

gesturestart,gesturechange,gestureend;

只有两个手指都触摸到事件的接收容器时才会触发这些事件;

在一个元素上设置事件处理程序,意味着两个手指必须同时位于该元素的范围之内,

才能触发手势事件(这个元素就是目标);

由于这些事件冒泡,所以将事件处理程序放在文档上也可以处理所有手势事件;

此时,事件的目标就是两个手指都位于其范围内的那个元素;

触摸事件和手势事件之间存在某种关系;

当一个手指放在屏幕上时,会触发touchstart事件;

如果另一个手指又放在了屏幕上,则会先触发gesturestart事件,随后触发基于该手指的touchstart事件;

如果一个或两个手指在屏幕上滑动,将会触发gesturechange事件;

但只要有一个手指移开,就会触发gestureend事件,紧接着又会触发基于该手指的touchend事件;

与触摸事件一样,每个手势事件的event对象都包含着标准的鼠标事件属性:

bubbles,cancelable,view,clientX,clientY,screenX,screenY,detail,altKey,shiftKey,ctrlKey和metaKey;

此外,还包含两个额外的属性:rotation和scale;

其中,rotation属性表示手指变化引起的旋转角度,

负值表示逆时针旋转,正值表示顺时针旋转(该值从0开始);

而scale属性表示两个手指间距离的变化情况(例如向内收缩会缩短距离);

这个值从1开始,并随距离拉大而增长,随距离缩短而减小;

function handleGestureEvent(event) {
    var output = document.getElementById("output");
    switch (event.type) {
        case "gesturestart":
            output.innerHTML = "Gesture started (rotation=" + event.rotation +
                ",scale=" + event.scale + ")";
            break;
        case "gestureend":
            output.innerHTML += "<br>Gesture ended (rotation=" + event.rotation +
                ",scale=" + event.scale + ")";
            break;
        case "gesturechange":
            output.innerHTML += "<br>Gesture changed (rotation=" + event.rotation +
                ",scale=" + event.scale + ")";
            break;
    }
}

document.addEventListener("gesturestart", handleGestureEvent, false);
document.addEventListener("gestureend", handleGestureEvent, false);
document.addEventListener("gesturechange", handleGestureEvent, false);

触摸事件也会返回rotation和scale属性,但这两个属性只会在两个手指与屏幕保持接触时才会发生变化;

一般来说,使用基于两个手指的手势事件,要比管理触摸事件中的所有交互要容易得多;