JS学习12(事件)

事件流

IE和Netscape开发团队提出了完全相反的两种事件流的概念,事件冒泡流和事件捕获流。

事件冒泡

事件由最具体的元素开始,逐级向上传播到较不具体的元素,最终到文档。

事件捕获

事件捕获从document开始,逐级向下,最后传到最具体的节点。

DOM事件流

DOM2级事件定义的事件流包含3个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。
捕获阶段会从文档节点自上而下传递直到目标节点的上一个节点;处于目标阶段时传到目标节点,冒泡阶段开始向上传递知道文档节点。
规定是捕获阶段事件不传递到目标节点,但是大多数浏览器就传递到了,这就意味着有2次机会在目标对象上操作事件。

事件处理程序

响应某个事件的函数

HTML事件处理程序

可以使用HTML特性来指定。

<input type="button" value="Click Me" onclick="alert(event.type)">
<input type="button" value="Click Me" onclick="alert(this.value)">
<input type="button" value="hahaah" onclick="clickButton(this)">
function clickButton(element) {
    alert(element.value);
}

这里注意JS代码中如果出现&""<>要使用转义字符。
这样写事件处理程序是很不灵活的,HTML代码和JS代码耦合度太高。
不过这样写是有一些方便的地方的,这样写的JS语句会被包含在一个动态创建的函数中,这个函数中会存在一个局部变量event事件对象,而且通过this这个变量可以访问到元素本身。这个函数还会使用with拓展作用域,让你更方便的直接访问document及元素本身的成员:

function(){
    with(document){
        with(this){ 
            //你的代码   
        } 
    }
}
//于是可以直接这样写
<input type="button" value="Click Me" onclick="alert(value)">

如果要调用函数,这个函数在JS中要处于全局作用域哦,而且要使用this对象要将this作为参数传进去,否则是访问不到的。
这样写事件处理程序是有问题的,一个是紧耦合的问题,一个是时间差,如果你的JS文件是放在最下面的,有可能会出现函数已经绑在事件上了可是JS却还没给出函数定义,这样就会报错,为了避免这样的情况出现,我们使用try-catch:

<input type="button" value="Click Me" onclick="try{clickButton(this);}catch(ex){}">

不过还是不那么理想是吧。

DOM0级事件处理程序

每个元素都有自己的事件处理程序属性,以这种方式添加的事件会在事件流的冒泡阶段被处理。传入的处理函数是在元素的作用域中运行。将这个属性指向空就取消了事件绑定,值得一提的是,如果你使用上面的HTML特性指定事件处理函数,这个属性里就包含着HTML里你写的事件函数的代码,置空也同样可以取消绑定。

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    alert(this.id);    //"myBtn"
};
btn.onclick = null;

DOM2级事件处理程序

IE8及以下不支持DOM2级事件
DOM2级事件定义了两种方法:addEventListener() removeEventListener()。这两个方法接受3个参数,要处理的事件名,处理函数,和一个布尔值。这个布尔值代表在捕获节点调用事件处理程序(true)还是在冒泡过程中调用。
这样添加意味着可以添加多个事件处理程序,在事件触发时会按照添加的顺序来执行。传入的处理函数是在元素的作用域中运行。
注销时要传入完全相同的函数才能注销,这就意味着如果你的处理函数是以匿名函数的方式传递进去的,那就不可能注销了哦,因为再传进去一个匿名函数也不是原先那个了:

var btn = document.getElementById("myButton");
var body = document.body;

//冒泡阶段
body.addEventListener("click", function(){
    alert("Hello world!");
}, false);
//捕获阶段
body.addEventListener("click", function(){
    alert("Hello world!");
}, true);
//如果是使用匿名函数注册的
btn.addEventListener("click", function(){
    alert(this.id + "匿名");
}, false);
//注销不掉
btn.removeEventListener("click", function(){
    alert(this.id);
}, false);
//这样就能注销掉了
var handler = function(){
    alert(this.id + "非匿名");
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);

IE事件处理程序

IE8及以下不支持DOM2级事件,但是支持两个类似方法attachEvent()、detachEvent()
只支持冒泡阶段
传入的处理函数是在全局作用域中运行
添加多个事件时触发顺序与添加顺序相反
注销时同样需要传入相同的参数,匿名函数无法注销。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert(this === window); //true
});
var handler = function(){
    alert("Clicked");
};
btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);

跨浏览器事件处理程序

优先使用DOM2级的,再不行使用IE专有的,最后使用DOM0级的(一般不可能用到)

var EventUtil = {
    addHandler: function(element, type, handler){
        if (element.addEventListener){
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent){
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    removeHandler: function(element, type, handler){
        if (element.removeEventListener){
            element.removeEventListener(type, handler, false);
        } else if (element.detachEvent){
            element.detachEvent("on" + type, handler);
        } else {
            element["on" + type] = null;
        } }
};
var btn = document.getElementById("myButton");
var handler = function(){
    alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);

事件对象

事件被触发时会产生一个事件对象,这个对象中包含着所有与事件有关的信息,包括导致事件的元素,事件的类型以及其他各个事件特定的信息。所有浏览器都支持event对象,但是实现不同。

DOM中的事件对象

兼容DOM的浏览器会将一个event对像传入到事件处理程序中,三种方式都有

btn.onclick = function(event){ 
    alert(event.type); //"click"
};
btn.addEventListener("click", function(event){
    alert(event.type); //"click" 
}, false);
<input type="button" value="Click Me" onclick="alert(event.type)"/>

所有的event对象都会有下面的方法:

属性/方法 类型 读写 说明
bubbles Boolean 只读 事件是否冒泡
cancelable Boolean 只读 是否可以取消事件的默认行为
currentTarget Element 只读 当前正在处理事件的那个元素
defaultPrevented Boolean 只读 是否已经调用了preventDefault()。DOM3
detail Integer 只读 与事件相关的细节信息
eventPhase Integer 只读 触发事件的阶段,1捕获,2处于目标,3冒泡
preventDefault() Function 只读 取消事件的默认行为
stopImmediatePropagation() Function 只读 取消进一步的捕获或冒泡,同时阻止任何事件处理程序被调用。DOM3
stopPropagation() Function 只读 取消进一步的捕获或冒泡
target Element 只读 事件的目标
trusted Boolean 只读 true表示是由浏览器生成的事件,false说明是JS创建的。DOM3
type String 只读 事件的类型
view AbstractView 只读 与事件关联的抽象视图,等同于发生事件的window对象

关于currentTarget的解释
在事件处理程序的内部,this始终指向currentTarget的值,而target则只包含事件的实际目标。

//当事件处理函数就在按钮上时可能看不出什么区别,但是当在按钮的父元素上时就不同了
document.body.onclick = function(event){ 
    alert(event.currentTarget === document.body); //true 
    alert(this === document.body); //true 
    alert(event.target === document.getElementById("myBtn")); //true 
};

preventDefault()
比如a标签的默认行为就是跳转到URL,使用event.preventDefault()可以阻止。
cancelable是true的事件才可以阻止。

var link = document.getElementById("myLink");
link.onclick = function(event){
    event.preventDefault();
};

IE中的事件对象

event在IE中是window的一个属性,可以直接获取

var btn = document.getElementById("myBtn");
btn.onclick = function(){
    var event = window.event;
    alert(event.type);     //"click"
};
btn.attachEvent("onclick", function(event){ 
    alert(event.type); //"click"
});
属性/方法 类型 读写 说明
cancelBubble Boolean 读写 默认为false,将其设置为true就可以取消冒泡
returnValue Boolean 读写 默认为true,设为false可以取消事件的默认行为
srcElement Element 只读 与target相同
type String 只读 事件的类型

要注意在IE中的事件处理程序最好不要使用this,使用event.srcElement比较保险

btn.attachEvent("onclick", function(event){
    alert(event.srcElement === this); //false
});

跨浏览器的事件对象

根据浏览器之间的区别,在刚才的EventUtil中添加下列方法:

getEvent: function(event){
    return event ? event : window.event;
},
getTarget: function(event){
    return event.target || event.srcElement;
},
preventDefault: function(event){
    if (event.preventDefault){
        event.preventDefault();
    } else {
        event.returnValue = false;
    }
},
stopPropagation: function(event){
    if (event.stopPropagation){
       event.stopPropagation();
    } else {
        event.cancelBubble = true;
    } 
}

这样使用~~

var link = document.getElementById("link");
var body = document.body;
var handler = function (event) {
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    EventUtil.preventDefault(event);
    EventUtil.stopPropagation(event);
    alert(target);
    alert(event.target);
}
link.addEventListener("click", handler, false);
body.addEventListener("click", handler, false);

事件类型

DOM3级事件规定了如下几类事件:

  • UI事件
  • 焦点事件
  • 鼠标事件
  • 滚轮事件
  • 文本事件
  • 键盘事件
  • 合成事件
  • 变动事件

除了这几类事件,HTML5也定义了一组事件,有的浏览器还有一些专有事件。

UI事件

  • DOMActivate:表示元素已经被用户操作,DOM3已经弃用了
  • load:当页面完全加载后在window上触发,当所有框架加载完毕时在框架集上触发,图像加载完时在img上触发,当嵌入内容加载完毕时在object元素上触发
  • unload:load对应的那些,完全卸载后触发
  • abort:在用户下载过程中,如果内容没下载完,在object元素上触发
  • error:JavaScript出错时在window上触发,无法加载图片时在omg上触发,有一个或多个框架无法加载时在框架集上触发
  • select:用户选择文本(input,texture)中的一个或多个字符时触发
  • resize:当窗口或框架大小改变时在window或框架触发
  • scroll:当用户滚动带滚动条的元素时在该元素上触发,body元素有页面的滚动条

要确定浏览器是否支持:

var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");

load事件
DOM2级事件规范是在document上触发load事件,不过为了兼容所有浏览器都在window上实现了load

EventUtil.addHandler(window, "load", function(){
    var image = document.createElement("img");
    EventUtil.addHandler(image, "load", function(event){
        event = EventUtil.getEvent(event);
        alert(EventUtil.getTarget(event).src);
    });
    //就算不吧img添加到文档,只要设置了SRC就会开始下载
    //document.body.appendChild(image);
    image.src = "http://www.feng.com/images/icon_01.png";
});

有的浏览器在script和link上也会有load事件

unload事件
这个事件在文档在完全被卸载后触发,只要用户从一个页面切换到另一个页面就回触发这个事件。利用这个事件可以清除引用

EventUtil.addHandler(window, "unload", function(event){
    alert("Unloaded");
});

resize事件

EventUtil.addHandler(window, "resize", function(event){
    alert("Resized");
});

浏览器可能会在窗口大小改变1px的时候就触发这个事件,要小心不要在这里放开销大的方法。
scroll事件
这个事件可以添加到任何有滚动条的元素上。也是会被重复触发的。页面的

EventUtil.addHandler(document.getElementById("scrollTest"), "scroll", function(event){
    alert("div Scrolled");
});
EventUtil.addHandler(window, "scroll", function(event){
    alert("window Scrolled");
});

焦点事件

与document.hasFocus()和document.activeElement配合可以知晓用户在页面上的行踪。

  • blur:在元素失去焦点时触发,不会冒泡
  • DOMFocusIn:元素获得焦点时触发,冒泡,DOM3废弃
  • DOMFocusOut:元素失去焦点,DOM3废弃
  • focus:元素获得焦点时触发,不冒泡
  • focusin:与focus等价,但冒泡,不是所有浏览器都支持,较新的都支持
  • focusout:失去焦点,冒泡,较新的支持

当焦点从一个元素移到另一个,依次触发下面的事件:

  1. focusout
  2. focusin
  3. blur
  4. DOMFocusOut
  5. focus
  6. DOMFocusIn

focusin和focus还是有一些区别的,试试这两个你就知道啦:

<input type="text" id="textField">
var btn = document.getElementById("textField");
btn.addEventListener("focus",function () {
    alert("button On Focus");
},false);
btn.addEventListener("focusin",function () {
    alert("button On Focus");
},false);

focus事件会在alert被关掉之后,焦点回到input时再次触发,alert会不停的出现。
focusing就只会alert一次。

鼠标与滚轮事件

DOM3级事件中定义了9个鼠标事件:

  • click:单击主鼠标按钮或按下回车键时触发
  • dblclick:双击主鼠标按钮
  • mousedown:用户按下任意鼠标按钮
  • mouseup:释放鼠标按钮时触发
  • mouseenter:光标从元素外首次移到元素内触发。不冒泡。而且在光标移动到后代元素上时不会触发。
  • mouseleave:移出元素时触发,不冒泡,移出后代元素时不会触发。
  • mousemove:在元素内部移动时重复触发
  • mouseout:指针位于一个元素上方,移入另一个元素时触发,移入的元素是当前元素的子元素时也会触发
  • mouseover:移入本元素时触发,移入本元素的某个子元素时也会触发
  • mousewheel:跟踪鼠标滚轮或触控板滚动

其中click事件的触发依赖在同一个元素上相继触发mousedown,mouse。dblclick则要在同一个元素上触发两次click。
客户区坐标位置
位置信息保存在clientX,clientY。表示鼠标事件发生时鼠标指针在视口中的位置。要注意的是,这个是指针相对于视口的位置,如果你的页面发生滚动而鼠标没动,这个坐标不会有变化。

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert(event.clientX + "," + event.clientY);
});

页面坐标位置
这个就真的是在页面中的位置了pageX,pageY

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Page coordinates: " + event.pageX + "," + event.pageY);
});

屏幕坐标位置
鼠标相对屏幕的坐标信息screenX,screenY
修改键
在鼠标按下的时候可以检查Shift、Ctrl、Alt、Meta哪个键被按下了。由此做一些特定的操作。由event的4个属性来检查:shiftKey、ctrlKey、altKey、metaKey。

var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    var keys = new Array();
    if (event.shiftKey){
        keys.push("shift");
    }
    if (event.ctrlKey){
        keys.push("ctrl");
    }
    if (event.altKey){
        keys.push("alt");
    }
    if (event.metaKey){
        keys.push("meta");
    }
    alert("Keys: " + keys.join(","));
});

相关元素
mouseover、mouserout这两个事件是有相关元素的。比如mouseover时你移出的那个元素就是相关元素。可以通过event的relatedTarget属性来获得。IE8及以下通过fromElement和toElement来获得。由于又有兼容问题,再次拓展我们的工具包咯:

getRelatedTarget: function(event){
    if (event.relatedTarget){
        return event.relatedTarget;
    } else if (event.toElement){
        return event.toElement;
    } else if (event.fromElement){
        return event.fromElement;
    } else {
        return null;
    }
}

鼠标按钮
click只在鼠标主键被按下时触发,所以并不存在检测按钮的问题,不过对于mousedown和mouseup来说就不一样了,在他们的event中有一个button属性。DOM的button属性有如下3个值:0主键,1滚轮键,2次键。
IE8之前也有button,不过差别比较大,有8个值:

  • 0:没有按钮
  • 1:主
  • 2:次
  • 3:主次同时
  • 4:滚轮
  • 5:主,滚轮同时
  • 6:次,滚轮同时
  • 7:3个同时

是时候拓展工具包了:

getButton: function(event){
    if (document.implementation.hasFeature("MouseEvents", "2.0")){
        return event.button;
    } else {
        switch(event.button){
            case 0:
            case 1:
            case 3:
            case 5:
            case 7:
                return 0;
            case 2:
            case 6:
                return 2;
            case 4:
                return 1;
        } 
    }
}

mousewheel事件
在滚轮滚动时触发,event有一个特殊的属性wheelDelta向上滚是负值,向下滚是正值。Opera早期版本相反。

EventUtil.addHandler(document, "mousewheel", function(event){
    event = EventUtil.getEvent(event);
    var delta = (client.engine.opera && client.engine.opera < 9.5 ?
        -event.wheelDelta : event.wheelDelta);
    alert(delta);
});

除firefox外都支持这个事件,firefox支持的是DOMMouseScroll。

EventUtil.addHandler(window, "DOMMouseScroll", function(event){
    event = EventUtil.getEvent(event);
    alert(event.detail);
});

触摸设备
触摸设备不支持dblclick事件,双击会放大页面
轻击可单击的元素会触发mousemove事件。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此发生变化,那么会依次发生mousedown、mouseup、click事件
轻击不可单击的元素不会触发任何事件。
可单击元素是指那些单击有默认操作的元素,或者你指定了onclick事件处理函数的元素。
mousemove事件也会触发mouseover和mouseout事件。
两个手指放在屏幕上且页面随手指移动而滚动时会触发mousewheel和scroll事件。
无障碍性问题
无障碍主要就是针对盲人的设计,盲人会使用屏幕阅读器,而屏幕阅读器使用键盘来触发事件。
所以尽量使用click事件,因为回车可以直接触发。
如果一定要使用那些键盘默认触发不了的操作,比如onmouseover、dblclick,应添加键盘快捷方式。

键盘与文本事件

键盘事件:

  • keydown:当用户按下任意键触发,按住不放重复触发
  • keypress:当用户按下键盘上字符键时触发,按住不放时重复触发
  • keyup:当用户释放键盘上的键时触发

文本事件:

  • textInput:文本插入文本框前会触发

在用户按下字符键时会触发keydown,紧接着keypress,如果是在文本框里输入文字这时会触发textInput,如果用户抬起来了按键就会触发keyup,不抬起来会重复触发keydown,keypress,textInput。
键盘事件也支持shiftKey、ctrlKey、altKey、metaKey。
键码
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,数字字母键就是ASCII码。
字符编码
发生keypress意味着按下的键会影响到屏幕中文本的显示,按下能插入或删除字符的键都会触发keypress事件。
属性charCode只有在keypress事件时才包含值,这个值是按下的那个键所带代表字符的ASCII码,而此时的keyCode可能等于0,也可能等于键码,所以跨浏览器要小心:

getCharCode: function(event){
    if (typeof event.charCode == "number"){
        return event.charCode;
    } else {
        return event.keyCode;
    }
}
EventUtil.addHandler(textbox, "keypress", function(event) {
    event = EventUtil.getEvent(event);
    alert(String.fromCharCode(EventUtil.getCharCode(event)));
});

DOM3级变化
DOM3中不再包含charCode,改为key和char。key就是键名字符串:“K”、“g”、“Shift”等。char在字符键情况下与key相同,非字符键时为null。
textInput事件
只有可编辑区域才会触发textInput。且只有有效的输入会触发。这个事件的事件对象里有个data,这个属性里保存的就是实实在在的输入数据,你输入a或A或粘贴进来的asdfasdf在这里都会得到体现。

var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function(event){ 
    event = EventUtil.getEvent(event);
    alert(event.data);
});

还有一个属性叫inputMethod,表示把文本输入到文本框的方式,粘贴,拖放,手写之类的,只有IE实现了。

复合事件

只有IE9+支持
DOM3级中新添加的事件用来处理IME的输入序列,IME是输入法编辑器,可以让用户输入在物理键盘上找不到的字符,IME通常需要按住多个键最终只输入一个字符。有3个事件:

  • compositionstart:IME被打开,这是event里的data属性是正在编辑的文本(比如已经被选中马上要被替换的文本,正常输入情况下一般是空)
  • compositionupdate:在向输入字段插入新字符时触发,data包含正插入的新字符
  • compositionend:IME关闭,返回正常键盘输入状态时,data包含此次插入的所有字符。
EventUtil.addHandler(textbox, "compositionstart", function(event){ 
    event = EventUtil.getEvent(event);
    alert(event.data);
});

变动事件

DOM2级的变动事件在DOM中某一部分发生变化时给出提示,跟少用到

  • DOMSubtreeModified
  • DOMNodeInserted
  • DOMNodeRemoved
  • DOMNodeInsertedIntoDocument
  • DOMNodeInserted
  • DOMNodeRemovedFromDocument
  • DOMNodeRemoved
  • DOMAttrModified
  • DOMCharacterDataModified

HTML5事件

contextmenu事件
这个事件在右键调出上下文菜单时被触发,可以通过取消默认事件来弹出自己的右键菜单,这个事件冒泡。

EventUtil.addHandler(document, "contextmenu", function(event){
    event = EventUtil.getEvent(event);
    EventUtil.preventDefault(event);
    var menu = document.getElementById("myMenu");
    menu.style.left = event.pageX + "px";
    menu.style.top = event.pageY + "px";
    menu.style.visibility = "visible";
});

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事件不同,这个不理会各种图片,JS等文件是否加载完成,只要完整的DOM结构OK了就触发。
这允许我们尽早的添加事件处理程序和DOM操作等。
这个事件的目标实际上是document,虽然会冒泡到windows

EventUtil.addHandler(document, "DOMContentLoaded", function(event){ 
    alert("Content loaded");
    alert(event.target);//[object HTMLDocument]
});

readystatechange事件
IE、Firfox 4+、Opera。
为某些元素提供的事件,目的是提供与文档或元素的加载状态有关的信息,支持这个事件的每个对象都有一个readyState属性,有5个可能值:uninitialized、loading、loaded、interactive、complete
interactive,complete不一定哪个先出现,所以要快的话两个一起检测,为避免执行两次,检测到了就注销事件

EventUtil.addHandler(document, "readystatechange", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    if (target.readyState == "loaded" || target.readyState == "complete"){
        EventUtil.removeHandler(target, "readystatechange", arguments. callee);
        alert("Content Loaded");
    }
});

pageshow和pagehide
有的浏览器有一种特性叫往返缓存,使用户在使用前进后退按钮时更快,这个缓存完全保存了整个页面,包括JS的执行状态,这就意味着不会再触发load事件。
pageshow无论这个页面是新打开的还是在往返缓存中的,都会在这个页面显示的时候触发。新打开的会在load后触发。
其event对象中有一个persisted属性,是true时代表是从往返缓存中恢复的。

(function(){
    var showCount = 0;
    EventUtil.addHandler(window, "load", function(){
        alert("Load fired");
    });
    EventUtil.addHandler(window, "pageshow", function(){
        showCount++;
        alert("Show has been fired " + showCount +
            " times. Persisted? " + event.persisted);
    });
})();

pagehide是在unload之前被触发的。
指定了onunload事件的页面不会被存在往返缓存中,因为onunload事件一般是为了撤销在load事件中做的事。
hashchange事件
这个事件在URL参数列表发生改变时触发,这个事件要添加到window对象

EventUtil.addHandler(window, "hashchange", function(event){
    alert("Current hash: " + location.hash);
});

设备事件

这些事件是针对移动设备的
orientationchange事件
这是Apple在移动版本的Safari中加入的事件,其window.orientation中可能包含3个值:0、90、-90。只要用户改变了设备的查看模式就会触发该事件。iOS设备都支持这个事件。

EventUtil.addHandler(window, "orientationchange", function(event){
   alert(window.orientation);
});

MozOrientation事件
这是火狐引入的检测设备旋转的事件,其event中有x、y、z三个值,有加速计的设备就可以用

EventUtil.addHandler(window, "MozOrientation", function(event){
    var output = document.getElementById("output");
    output.innerHTML = "X=" + event.x + ", Y=" + event.y + ", Z=" + event.z +"<br>";
});

deviceorientation事件
这个事件也是检测加速计来获得设备方向的变化,变化时在window上触发。设备在平躺时z轴向上。
事件的event对象中有如下5个属性:

  • alpha:围绕z轴旋转时,y轴的度数差,0~360的浮点数
  • beta:围绕x轴旋转时,z轴的度数差-180~180的浮点数
  • gamma:围绕y轴旋转时,z轴的度数差,-90~90的浮点数
  • absolute:布尔值,设备是否返回一个绝对值
  • compassCalibrated:布尔值,指南针是否校准
EventUtil.addHandler(window, "deviceorientation", function(event){
    alert("Alpha=" + event.alpha + ", Beta=" + event.beta + ", Gamma=" + event.gamma);
});

devicemotion事件
这个事件告诉开发人员设备什么时候移动,其事件对象包含以下属性:

  • acceleration:一个包含xyz的对象,排除重力每个方向的加速度
  • accelerationIncludingGravity:考虑重力,每个方向的加速度
  • interval:以毫秒表示的时间值,在每次事件中是常量。
  • rotationRate:包含表示方向的alpha、beta、gamma属性的对象

以上这些值读不到就是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;
    }
});

触摸与手势事件

触摸事件
在iOS和Android上实现了

  • touchstart:每根手指触摸屏幕时触发
  • touchmove:手指在屏幕上滑动时连续触发,调用preventDefault()可以阻止页面滚动
  • touchend:手指移开
  • touchcancel:系统停止跟踪触摸时触发
    上面几个事件都会冒泡,也都可以取消。
    event对象中常见的鼠标事件的属性都有:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey、metaKey。
    还包含三个用于跟踪触摸事件的属性:
  • touches:表示当前跟踪的触摸操作的Touch对象的数组
  • targetTouchs:特定于事件目标的Touch对象的数组
  • changeTouches:表示自上次触摸以来发生什么改变的Touch对象的数组

每个Touch对象有下列属性:

  • clientX
  • clientY
  • identifier:表示触摸的唯一ID
  • 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

手势事件
当两个手指触摸屏幕时会产生手势,iOS中的Safari定义了3个手势事件:
gesturestart

  • gesturestart:一个手指已在屏幕上另一个手指又触摸屏幕
  • gesturechange:当触摸屏幕的任何一个手指发生变化的时候
  • gestureend:任何一个手指移开

事件都冒泡
鼠标事件的标准属性都有
两个特殊事件:rotation、scale
rotation表示手指变化引起的旋转角度,负值表示逆时针
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);

内存和性能

在JS中添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。

事件委托

利用事件的冒泡特性,在DOM树种尽量高的层次上添加一个事件处理程序:

EventUtil.addHandler(document, "click", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    switch(target.id){
        case "myButton":
            document.title = "I changed the document's title";
            break;
        case "highDiv":
            location.href = "http://www.wrox.com";
            break;
        case "myDiv":
            alert("hi");
            break;
    }
});

这样做优点很多

  • 只需要取得一个DOM元素或直接加在document上,快
  • 只添加了一个事件处理函数,内存占用小
  • 如果直接添加在document上,document不需要等待load事件,也就是说按钮只要出现立即可用

移除事件处理程序

事件与元素的连接越多,页面就越慢。限制事件数量刚才我们已经做了。另外就是在不需要的时候移除事件处理程序。
当我们移除或替换节点时,原有节点的事件处理程序却还在,这时最好手工将其事件解绑。如果一开始你的事件处理就像刚才那样写在了高层次的元素中,这个问题就不存在啦。
还有就是在我们卸载页面时,有的浏览器处理的并不好,页面被卸载了但是处理程序还在,来回来回切换打开的过程中内存里就多了很多没用或重复的事件。我们可以在onunload事件中移除所有事件处理程序。这就体现了将事件处理集中起来的优点——移除时很轻松。

模拟事件

通过JS来触发本该由浏览器触发的事件

DOM中的事件模拟

鼠标事件

var div = document.getElementById("myDiv");
//先创建一个事件,会返回一个特定的事件对象
var event = document.createEvent("MouseEvents");
//用各种参数初始化事件对象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0,
false, false, false, false, 0, null);
//在目标元素上触发
div.dispatchEvent(event);

可以模拟下面这些事件UIEvents、MouseEvents、MutationEvents、HTMLEvents。具体的就不写了大体一致的思路。

IE中的事件模拟

一样的思路,方法不太一样

var btn = document.getElementById("myBtn");
var event = document.createEventObject();
event.screenX = 100;
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;
btn.fireEvent("onclick", event);

推荐阅读更多精彩内容