ThinkSSL🔒 一键申购 5分钟快速签发 30天无理由退款 购买更放心 广告
## Event 事件 客户端JavaScript程序采用了异步事件驱动编程模型。 **一、相关术语** `事件流`描述的是从页面中接收事件的顺序。 `事件`就是Web浏览器通知应用程序发生了什么事情。 `事件类型(event type)`是一个用来说明发生什么类型事件的字符串。例如,“mousemove”表示用户移动鼠标,“keydown”表示键盘上某个键被按下等等。 `事件目标(event target)`是发生的事件与之相关的对象。当讲事件时,我必须同时指明类型和目标。比如:window上的load事件或`<button>`元素的click事件。 在客户端JavaScript应用程序中,Window、Document和Element对象是最常见的事件目标。 `事件处理程序(event handler)`或`事件监听程序(event listener)`是处理或响应事件的函数。 `事件对象(event object)`是与特定事件相关且包含有关该事件详细信息的对象。事件对象作为参数传递给事件处理程序函数(不包括IE8及之前版本,在这些浏览器中有时仅能通过全局变量event才能得到)。所有的事件对象都用来指定事件类型的type属性和指定事件目标的target属性。(在IE8及之前的版本中用srcElement而非target) `事件传播(event propagation)`是浏览器决定哪个对象触发其事件处理程序的过程。当文档元素上发生某个类型的事件时,它们会在文档树上向上传播或“冒泡“(bubble)。 事件传播的另一种方式:`事件捕获(event capturing)`:在容器元素上注册的特定处理程序有机会在事件传播到真实目标之前捕获它。 **二、Event事件** **10.1注册事件处理程序** 注册事件处理程序有两种基本方式: **(1)一种是给事件目标对象或文档元素设置属性。** 按照约定,事件处理程序属性的名字由“on”后面跟着事件名组成:onclick、onchange等。这些属性名是区分大小写的,所有都是`小写`,即使是事件类型是由多个词组成的(比如“readystatechange”)。 ``` <div onclick="alert(1);"></div> ``` 注:尽量少用内联事件 还可以这样: ``` var div = document.querySelector('div'); div.onclick=function(){ alert(1); } ``` 如果要删除"on"类型的事件,只需将其设为null: ``` div.onclick = null; ``` 再点击就不会有任何反应。 事件处理程序属性的缺点是其设计都是围绕着假设每个事件目标对于每种事件类型将最多只有一个处理程序。 **(2)另一种是通过addEventListener()** window对象、Document对象和所有的文档元素(Element)都定义了一个名为`addEventListener()`方法,使用这个方法可以为事件目标注册事件处理程序。 ``` target.addEventListener(type, listener[, useCapture]); ``` addEventListener方法接受三个参数。 - type:事件名称(事件类型),字符串,大小写不敏感。 - listener:监听函数。事件发生时,会调用该监听函数。 - useCapture:布尔值,表示监听函数是否在捕获阶段(capture)触发,默认为false(监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。 使用addEventListener()方法时,事件类型不应包括前缀“on”,比如:“onclick”改成“click”等。 ``` addEventListener('click',listener,false); ``` 注意:调用addEventListener()并不会影响onclick属性的值。 ``` <button id="mybutton">点击</button> var v = document.getElementById('mybutton'); v.onclick = function() {alert('1');} v.addEventListener('click',function(){alert('2');},false); ``` 上面的代码中,单击按钮会产生两个alert()对话框。 能通过多次调用addEventListener()方法为同一个对象注册同一事件类型的多个处理程序函数。 所有该事件类型的注册处理程序都会按照注册的顺序调用。使用相同的参数在同一个对象上多次调用addEventListener()是没用的,处理程序仍然只注册一次,同时重复调用也不会改变调用处理程序的顺序(也就是说,如果为同一个事件多次添加同一个监听函数,函数只会执行一次,多余的添加将自动删除)。 相对`addEventListener()`的是 `removeEventListener() `方法。 removeEventListener方法的参数,与addEventListener方法完全一致。它的第一个参数“事件类型”,也是大小写不敏感。 注意:removeEventListener()方法的事件处理程序函数必须是函数名。 **dispatchEvent()** dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。 ``` target.dispatchEvent(event) ``` dispatchEvent方法的参数是一个Event对象的实例。 **在IE上** IE支持事件冒泡流,不支持事件捕获流。 IE9以前的IE不支持`addEventListener()`和`removeEventListener()`。不过我们可以使用类似的方法`attachEvent()`和`detachEvent()`; `attachEvent()`和`detachEvent()`方法的工作原理与addEventListener()和removeEventListener()类似,但有所区别: - 因为IE事件模型不支持事件捕获,所以attachEvent()和detachEvent()只有两个参数:事件类型和处理程序函数 - IE方法的第一个参数使用了带“on”前缀的事件处理程序属性名。例如:当给addEventListener()传递“click”时,要给attachEvent()传递“onclick” - attachEvent()允许相同的事件处理程序函数注册多次。当特定的事件类型发生时,注册函数的调用次数和注册次数一样。 下面的代码中,创建一个EventUtil工具类,可以兼容ie浏览器: ``` var EventUtil = { addHandler: function(element, type, handler, useCapture) { if(element.addEventListener) { element.addEventListener(type, handler, useCapture ? true : false); } else if(element.attachEvent) { element.attachEvent('on' + type, handler); } else if(element != window) { element['on' + type] = handler; } }, removeHandler: function(element, type, handler, useCapture) { if(element.removeEventListener) { element.removeEventListener(type, handler, useCapture ? true : false); } else if(element.detachEvent) { element.detachEvent('on' + type, handler); } else if(element != window) { element['on' + type] = null; } } }; ``` 在上面的EventUtil工具类中,我们创建addHandler()用来绑定事件,removeHandler用来删除事件。 **10.2 事件处理程序的调用** 一旦注册了事件处理程序,浏览器就会在指定对象上发生指定类型事件时自动调用它。 **10.2.1 事件处理程序的参数** 通常调用事件处理程序时把`事件对象(event)`作为它们的一个参数。事件对象的属性提供了有关事件的详细信息。例如,type属性指定了发生的事件类型。 在IE8及以前的版本中,通过设置属性注册事件处理程序,当调用它们时并未传递事件对象。取而代之,需要通过全局对象window.event来获得事件对象。 下面的代码就是考虑了兼容性: ``` function handler(event){ event = event || window.event; } ``` **10.2.2 event事件对象** **(1)DOM中的事件对象** event对象包含与创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都有下列属性和方法: - bubbles 布尔值,只读,表示事件是否冒泡 - cancelable 布尔值,只读,表示是否可以取消事件的默认行为 - currentTarget Element类型,只读,其事件处理程序当前正在处理事件的那个元素 - defaultPrevented 布尔值,只读,为true时表示已经调用了preventDefault() - detail Integer类型,只读,与事件相关的细节信息 - eventPhase Integer类型,只读,调用事件处理程序的阶段:1表示捕获阶段,2表示目标阶段,3表示冒泡阶段 - preventDefault() Function类型,只读,取消事件的默认行为 - stopImmediatePropagation() Function类型,只读,取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用 - stopPropagation() Function类型,只读,取消事件的进一步捕获或冒泡 - target Element,只读,事件的目标 - trusted 布尔值,只读,为true时表示事件是浏览器生成的,为false时表示事件是开发人员创建的 - type String类型,只读,事件类型 - view AbstractView,只读,与事件关联的抽象视图,等同于发生事件的window对象 在事件处理程序内部,对象this始终等于currentTarget的值,而target则表示实际的目标。 **(2)IE中的事件对象** 对于IE,event对象是绑定在window对象中的: ``` window.event ``` IE的event对象也同样包含与创建它的事件相关的属性和方法,也具有一些共同属性和方法: - cancelBubble 布尔值,可读写,默认值为false,将其设置为true时,作用和DOM中的stoPropagation()方法一样 - returnValue 布尔值,可读写,默认为true,将其设为false时,作用和DOM中的preventDefault()方法的作用一样。 - srcElement Element,只读,事件的目标(与DOM中的target属性相同) - type String,只读,事件类型 基于IE和DOM事件对象不一样,我们可以工具类:EventUtil里添加方法: ``` var EventUtil = { addHandler: function(element, type, handler, useCapture) { // 省略代码 }, removeHandler: function(element, type, handler, useCapture) { // 省略代码 }, getEvent: function(event){ return event || window.event; }, getTarget: function(evetn){ 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; } } }; ``` 我给工具类EventUtil添加了4个新方法,第一个是getEvent(),返回对event对象的引用;第二个是getTarget(),返回事件的目标;第三个是preventDefault(),用于取消事件的默认行为;第四个是stopPropagation(),用来取消事件冒泡。 调用方式也很简单: ``` div.onclick = function(event){ event = EventUtil.getEvent(event); target = event.getTarget(); EventUtil.preventDefault(); EventUtil.stopPropagation(); } ``` **10.2.2 事件处理程序的运行环境** 当通过设置属性注册事件处理程序时,看起来就好像是在文档元素上定义了新方法: ``` e.onclick=function(){} ``` 事件处理程序在事件目标上定义,所以它们作为这个对象的方法来调用。也就是说,在事件处理程序内,this关键字指向事件目标。 **10.2.3 事件处理程序的作用域** 事件处理程序在其定义的作用域而非调用时的作用域中执行,并且它们能存取那个作用域中的任何一个本地变量。 **10.2.4 事件处理程序的返回值** 通过设置对象属性或HTML属性注册事件处理程序的返回值有时是非常有意义的。通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作。比如,表单提交按钮的onclick事件处理程序能返回false阻止浏览器提交表单。 ``` v.onclick = function() { return false; } ``` 理解事件处理程序的返回值只对通过属性注册的处理程序才有意义。 **10.2.5 调用顺序** 文档元素或其他对象可以指定事件类型注册多个事件处理程序。当适当的事件发生时,浏览器必须按照下面的规则调用所有的事件处理程序: - 通过设置对象属性或HTML属性注册的处理程序一直优先调用。 - 使用addEventListener()注册的处理程序按照它们的注册顺序调用。 - 使用attachEvent()注册的处理程序可能按照任何顺序调用,所以代码不应该依赖于调用顺序。 **10.2.6 事件传播** 当事件目标是Window对象或其他一些单独对象(比如XMLHttpRequest)时,浏览器会简单的通过调用对象上适当的处理程序响应事件。 在调用在目标元素上注册的事件处理函数后,大部分事件会“冒泡”到DOM树根。 发生在文档元素上的大部分事件都会冒泡,但有些例外,比如focus、blur和scroll事件。文档元素上的load事件会冒泡,但它会在Document对象上停止冒泡而不会传播到Window对象。只有当整个文档都加载完毕时才会触发window对象的load事件。 当事件目标是文档或文档元素时,它会在不同的DOM节点之间传播(propagation)。 分为三个阶段: - 捕获阶段(capture phase):从window对象传导到目标对象。(window--document--....--目标对象) - 目标阶段(target phase):目标对象本身的事件处理程序调用。 - 冒泡阶段(bubbling phase):从目标对象传导回window对象。(目标对象--父元素--....--document--window) ``` <!DOCTYPE html> <html> <head> <title></title> </head> <body> <div id="myDiv">点击</div> </body> </html> //事件捕获阶段,click事件的传播顺序 window document <html> <body> <div> // 目标阶段 div // 事件冒泡阶段,click事件的传播顺序 <div> <body> <html> document window ``` **事件代理(事件委托)** 基于事件会在冒泡阶段向上传播到父节点,我们可以将子节点的监听事件定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。 ``` <div id="div"> <div id="item">123</div> </div> document.getElementById('div').addEventListener('click', function(e) { var target = e.target; if(target.getAttribute('id').toLowerCase() == 'item') { alert(1); } }); ``` 如果使用事件代理,以后插入的新节点仍然可以监听的到。 如果使用JQuery,我们要为新增节点添加事件,除了在新增事件后添加事件外,还可以用下面的代码: ``` $(document).on('click','div',function(){}) ``` 这种方式其实就是使用了事件代理。 **10.2.7 事件取消** 用属性注册的世界处理程序的返回值能用于取消事件的浏览器默认操作。在支持addEventListener()的浏览器中,也能通过调用事件对象的preventDefault()方法取消事件的默认操作。 在IE9之前的IE中,可以通过设置事件对象的returnValue属性为false来达到同样的效果。 ``` function cancelHandler(event){ var event = event || window.event; if(event.preventDefault) {event.preventDefault();} //标准 if(event.returnValue) { event.returnValue = false;} // IE return false; //用于处理使用对象属性注册的处理程序 } ``` Event对象提供了一个属性defaultPrevented,返回一个布尔值,默认false,表示该事件是否调用过preventDefault方法。 **取消事件传播** 在支持addEventListener()的浏览器中,可以调用事件对象的一个stopPropagation()方法以阻止事件的继续传播。 ``` e.stopPropagation() //IE e.cancelBubble = true; ``` 在Event对象上还有一个方法`stopImmediatePropagation()`,阻止同一个事件的其他监听函数被调用。也就是说,如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。 ``` e.addEventListener('click',function(event){ event.stopImmediatePropagation(); }); e.addEventListener('click',function(event){ //不会触发 }); ``` **10.3 文档事件** **(1)beforeunload事件、unload事件、load事件、error事件、pageshow事件、pagehide事件** **beforeunload** 当浏览器将要跳转到新页面时触发这个事件。如果事件处理程序返回一个字符串,那么它将出现在询问用户是否想离开当前页面的标准对话框中。 ``` window.addEventListener('beforeunload',function(e){ var message = '你确认要离开吗!'; e.returnValue = message; return message }); ``` **unload** unload事件在窗口关闭或者document对象将要卸载时触发,发生在window、body、frameset等对象上面。 它的触发顺序排在beforeunload、pagehide事件后面。unload事件只在页面没有被浏览器缓存时才会触发,换言之,如果通过按下“前进/后退”导致页面卸载,并不会触发unload事件。 **load、error** `load`事件直到文档加载完毕时(包括所有图像、JavaScript文件、CSS文件等外部资源)才会触发。 `error`事件在页面加载失败时触发。注意,页面从浏览器缓存加载,并不会触发load事件。 这两个事件实际上属于进度事件,不仅发生在document对象,还发生在各种外部资源上面。浏览网页就是一个加载各种资源的过程,图像(image)、样式表(style sheet)、脚本(script)、视频(video)、音频(audio)、Ajax请求(XMLHttpRequest)等等。这些资源和document对象、window对象、XMLHttpRequestUpload对象,都会触发load事件和error事件。 **pageshow、pagehide** 默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。 pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数。 pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时,这个属性是false;当页面从缓存加载时,这个属性是true。 pagehide事件与pageshow事件类似,当用户通过“前进/后退”按钮,离开当前页面时触发。 pagehide事件的event对象有一个persisted属性,将这个属性设为true,就表示页面要保存在缓存中;设为false,表示网页不保存在缓存中,这时如果设置了unload事件的监听函数,该函数将在pagehide事件后立即运行。 **(2)DOMContentLoaded事件、readystatechange事件** `DOMContentLoaded`事件:当文档加载解析完毕且所有延迟(deferred)脚本(图片未加载完毕)都执行完毕时会触发,此时图片和异步(async)脚本可能依旧在加载,但是文档已经为操作准备就绪了。也就是说,这个事件,发生在load事件之前。 ``` document.addEventListener('DOMContentLoaded',handler,false); ``` `readystatechange`事件:document.readyState属性会随着文档加载过程而变,而每次状态改变,Document对象上的readystatechange事件都会触发。 ``` document.onreadystatechange = function() { if(document.readyState == 'complete'){ } } ``` **(3)scroll事件、resize事件** `scroll`事件在文档(window)或文档元素滚动时触发,主要出现在用户拖动滚动条。 `resize`事件在改变浏览器窗口大小时触发,发生在window、body、frameset对象上面。 **(4)hashchange事件、popstate事件** hashchange事件在URL的hash部分(即#号后面的部分,包括#号)发生变化时触发。如果老式浏览器不支持该属性,可以通过定期检查location.hash属性,模拟该事件。 popstate事件在浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()或history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()、history.forward()、history.go()时触发。 **(5)cut事件、copy事件、paste事件** 这三个事件属于文本操作触发的事件。 - cut事件:在将选中的内容从文档中移除,加入剪贴板后触发。 - copy事件:在选中的内容加入剪贴板后触发。 - paste事件:在剪贴板内容被粘贴到文档后触发。 这三个事件都有一个clipboardData只读属性。该属性存放剪贴的数据,是一个DataTransfer对象。 **(6)焦点事件** 焦点事件发生在Element节点和document对象上。 - focus事件:Element节点获得焦点后触发,该事件不会冒泡。 - blur事件:Element节点失去焦点后触发,该事件不会冒泡。 - focusin事件:Element节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡。Firefox不支持该事件。 - focusout事件:Element节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡。Firefox不支持该事件。 这四个事件的事件对象,带有target属性(返回事件的目标节点)和relatedTarget属性(返回一个Element节点)。对于focusin事件,relatedTarget属性表示失去焦点的节点;对于focusout事件,表示将要接受焦点的节点;对于focus和blur事件,该属性返回null。 由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。 **10.4 鼠标事件** **(1)click** click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。 “鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。 **(2)contextmenu** contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。 可以通过下面的方式阻止“上下文菜单”的出现: ``` document.oncontextmenu=function(){ return false; } ``` **(3)dblclick** dblclick事件当用户在element、document、window对象上,双击鼠标时触发。该事件会在mousedown、mouseup、click之后触发。 **(4)mousedown、mouseup** mouseup事件在释放按下的鼠标键时触发。 mousedown事件在按下鼠标键时触发。 **(5)mousemove** mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。 **(6)mouseover、mouseenter** mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。 两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。 **(7)mouseout、mouseleave** mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。 除了“mouseenter”和“mouseleave”外的所有鼠标事件都能冒泡。链接和提交按钮上的click事件都有默认操作且能够阻止。可以取消上下文菜单事件来阻止显示上下文菜单。 传递给鼠标事件处理程序的事件对象有clientX和clientY属性,它们指定了鼠标指针相对于包含窗口的坐标。加入窗口的滚动偏移量可以把鼠标位置转换成文档坐标。 **MouseEvent对象的属性** **(1)button、buttons** `button`属性指定当事件发生时哪个鼠标按键按下。 - -1:没有按下键。 - 0:按下主键(通常是左键)。 - 1:按下辅助键(通常是中键或者滚轮键)。 - 2:按下次键(通常是右键)。 buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。 - 1:二进制为001,表示按下左键。 - 2:二进制为010,表示按下右键。 - 4:二进制为100,表示按下中键或滚轮键。 同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。 注意:IE中的button属性拥有不同的参数: - 1:鼠标左键 - 4:鼠标中键 - 2:鼠标右键 **(2)clientX,clientY** `clientX`属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。 `clientY`属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。 **(3)movementX,movementY** - movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。 - movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。 **(4)screenX,screenY** `screenX`属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。 `screenY`属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。 **(7)pageX、pageY** `pageX`和`pageY`分别是触点相对HTML文档左边沿的X坐标和触点相对HTML文档上边沿的Y坐标。只读属性。 当存在滚动的偏移时,pageX包含了水平滚动的偏移,pageY包含了垂直滚动的偏移。 **(6)relatedTarget** `relatedTarget`属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。 **10.5 鼠标滚轮事件** 所有的现代浏览器都支持鼠标滚轮,并在用户滚动滚轮时触发事件。浏览器通常使用鼠标滚轮滚动或缩放文档,但可以通过取消mousewheel事件来阻止这些默认操作。 所有浏览器都支持“mousewheel”事件,但Firefox使用“DOMMouseScroll”事件。 传递给“mousewheel”处理程序的事件对象有wheelDelta属性,其指定用户滚动滚轮有多远(根据这个判断滚动方向)。 远离用户方向的一次鼠标滚轮“单击”的wheelDelta值通常是120,而接近用户方向的一次“单击”的值是-120。返回的总是120的倍数(120表明mouse向上滚动,-120表明鼠标向下滚动) 在Safari和Chrome中,为了支持使用二维轨迹球而非一维滚轮的Apple鼠标,除了wheelDelta属性外,事件对象还有wheelDeltaX和wheelDeltaY,而wheelDelta和wheelDeltaY的值一直相同。 而在Firefox中,传递给“DOMMouseScroll”的属性是detail。不过, detail属性值的缩放比率和正负符号不同wheelDelta,detail值乘以-40和wheelDelta值相等。记录其滚动距离的是“detail”属性,它返回的是3的倍数(3表明mouse向下滚动,-3表明mouse向上滚动)。 ``` window.onmousewheel = document.onmousewheel = scrollWheel; function scrollWheel(e){ e = e || window.event; if(e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件 if(e.wheelDelta > 0) { //当滑轮向上滚动时 } else if(e.wheelDelta < 0) { //当滑轮向下滚动时 }; } else if(e.detail) { //Firefox滑轮事件 if(e.detail < 0) { //当滑轮向上滚动时 } else if(e.detail > 0) { //当滑轮向下滚动时 }; }; } ``` **10.6 键盘事件** 键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。 keydown:按下键盘时触发该事件。 keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件。 keyup:松开键盘时触发该事件。 textinput 任何时候,只要用户输入文本都会触发。在Webkit浏览器中支持“textInput”事件。 事件对象属性data(保存输入文本),inputMethod属性(用于指定输入源) 注意:keypress和textinput事件是在新输入的文本真正插入到聚焦的文档元素前触发的。 如果用户一直按键不松开,就会重复触发keydown、keypress,直到用户松开才会触发keyup。 **属性** **keycode** 指定了输入字符的编码。在Firefox中使用的是charCode属性。 **altKey,ctrlKey,metaKey,shiftKey** altKey、ctrlKey、metaKey和shiftKey属性指定了当事件发生时是否有各种键盘辅助键按下。 altKey属性:alt键 ctrlKey属性:key键 metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键) shiftKey属性:Shift键 key,charCode key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。 主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。 charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。 **String.fromCharCode()** 一个keypress事件表示输入的单个字符。事件对象以数字Unicode编码的形式指定字符,所以必须用String.fromChatCode()把它转换成字符串。 **10.7 表单事件** **(1)input、propertychange** 检测文本输入元素的value属性改变,这两个事件是在新输入的文本真正插入到聚焦的文档元素前触发的。 一般用`<inupt>`和`<textarea>`里,不过,当将contenteditable属性设置为true时,只要值变化,也会触发这两个事件。 **(2)change** 当`<input>`、`<select>`和`<textarea>`的值发生变化时都会触发change事件。只有全部修改完成时它才会触发,这也是它和input事件的区别。 具体分下面几种情况: - 激活单选框(radio)或复选框(checkbox)时触发。 - 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。 - 当文本框或textarea元素的值发生改变,并且丧失焦点时触发。 **(3)select** 当`<input>`和`<textarea>`中选中文本时触发select事件。 **(4)reset、submit** 这两个事件是发生在表单对象上,而不是发生在表单的成员上。 reset事件:当表单重置(所有表单成员的值变回默认值)时触发。 submit事件:当表单数据向服务器提交时触发。 注意:submit事件的发生对象是form元素,而不是button元素(即使它的类型是submit),因为提交的是表单,而不是按钮。 **10.8 触控事件** 触控事件提供了响应用户对触摸屏或触摸板上操作的能力。 触控API提供了下面三个接口 - TouchEvent:代表当触摸行为在平面上发生变化时发生的事件 - Touch:代表用户与触摸屏幕间的一个接触点 - TouchList:代表一系列的Touch;一般在用户多个手指同时解除屏幕时使用 **10.8.1 TouchEvent** TouchEvent是一类描述手指在触摸平面的状态变化的事件。 **(1)触摸事件的类型** - touchstart:用户接触触摸屏时触发,它的target属性返回发生触摸的Element节点。 - touchend:用户不再接触触摸屏时(或者移出屏幕边缘时)触发,它的target属性与touchstart事件的target属性是一致的,它的changedTouches属性返回一个TouchList对象,包含所有不再触摸的触摸点(Touch对象)。 - touchmove:用户移动触摸点时触发,它的target属性与touchstart事件的target属性一致。如果触摸的半径、角度、力度发生变化,也会触发该事件。 - touchcancel:触摸点取消时触发,比如在触摸区域跳出一个弹出框窗口(modal window)、触摸点离开了文档区域(进入浏览器菜单栏区域)、用户放置更多的触摸点(自动取消早先的触摸点)。 - touchenter:当触点进去某个elemen时触发。没有冒泡过程。 - touchleave:当触点离开某个element时触发。没有冒泡过程。 **(2)TouchEvent的属性** **键盘属性** 以下属性都为只读属性,返回一个布尔值,表示触摸的同时,是否按下某个键。 altKey 是否按下alt键 ctrlKey 是否按下ctrl键 metaKey 是否按下meta键 shiftKey 是否按下shift键 **changedTouches** 返回一个TouchList对象,包含了代表所有从上一次触摸事件到此次事件过程中,状态发生了改变的触点的Touch对象。只读属性。 **targetTouches** 返回一个TouchList对象,包含了所有当前接触触摸平面的触点的Touch对象(也可以说是处于活动状态的触点的Touch对象)。只读属性。 **touches** 返回一个TouchList对象,包含了所有当前接触触摸平面的触点的Touch对象。只读属性。 **type** 此次触摸事件的类型。 **target** 此次触摸事件的目标元素(element)。这个目标元素对应 TouchEvent.changedTouches 中的触点的起始元素。 **10.8.2 Touch** Touch对象表示在触控设备上的触控点。通常是指手指或触控笔在触屏设备上的操作。 每个Touch对象代表一个触点,每个触点由其位置、大小、形状、压力大小和目标element描述。 **(1)Touch属性** 以下属性描述了用户的触摸行为 **identifier** 此Touch对象的唯一标识符。一次触摸动作在平面上移动的整个过程中,该标识符不变,可以根据它来判断跟踪是否在同一次触摸过程。只读属性。 **screenX、screenY** screenX和screenY分别是触点相对屏幕左边沿的X坐标和触点相对屏幕上边沿的Y坐标。只读属性。 **clientX、clientY** clientX和clientY分别是触点相对于可视区左边沿的X坐标和触点相对可视区上边沿的Y坐标。两个属性都不包括任何滚动偏移。只读属性。 **pageX、pageY** pageX和pageY分别是触点相对HTML文档左边沿的X坐标和触点相对HTML文档上边沿的Y坐标。只读属性。 当存在滚动的偏移时,pageX包含了水平滚动的偏移,pageY包含了垂直滚动的偏移。 **radiusX、radiusY、rotationAngle** radiusX:能够包围用户和触摸平面的接触面的最小椭圆的水平轴(X轴)半径. 这个值的单位和 screenX 相同。只读属性。 radiusY:能够包围用户和触摸平面的接触面的最小椭圆的垂直轴(Y轴)半径. 这个值的单位和 screenY 相同。只读属性。 rotationAngle:由radiusX 和 radiusY 描述的正方向的椭圆,需要通过顺时针旋转这个角度值,才能最精确地覆盖住用户和触摸平面的接触面,单位为度数,在0到90度之间。只读属性。 **force** 手指挤压触摸平面的压力大小, 从0.0(没有压力)到1.0(最大压力)的浮点数. 只读属性. **target** 当这个触点最开始被跟踪时(在 touchstart 事件中), 触点位于的HTML元素。也就是触摸发生时的那个节点。 **10.8.3 TouchList** 一个TouchList代表一个触摸平面上所有触点的列表。比如一个用户用三根手指接触平面,与之相关的TouchList对于每根手指都会生成一个Touch对象,共计三个。 **(1)TouchList的属性** **length** 返回TouchList中Touch对象的数量,只读属性。 **(2)方法** **identifiedTouch()** 返回列表中标识符与指定值匹配的第一个Touch对象。 **item()** 返回列表中以指定索引值的Touch对象。也可以使用数组的语法:touchlist[index] **10.8.4 其他触控事件** gesturestart、gestureend scale、rotation **10.9 进度事件** 进度事件用来描述一个事件进展的过程。比如XMLHttpRequest对象发出的HTTP请求的过程、`<img>、<audio>、<video>、<style>、<link>`加载外部资源的过程。下载和上传都会发生进度事件。 进度事件有以下几种: - abort事件:当进度事件被中止时触发。如果发生错误,导致进程中止,不会触发该事件。 - error事件:由于错误导致资源无法加载时触发。 - load事件:进度成功结束时触发。 - loadstart事件:进度开始时触发。 - loadend事件:进度停止时触发,发生顺序排在error事件\abort事件\load事件后面。 - progress事件:当操作处于进度之中,由传输的数据块不断触发。 - timeout事件:进度超过限时触发。 **10.11 拖放事件** 拖放(Drag-and-Drop,DnD)是在“拖放源(drag source)”和“拖放目标(drop target)”之间传输数据的用户界面。 拖拉的对象有好几种,包括Element节点、图片、链接、选中的文字等等。在HTML网页中,除了Element节点默认不可以拖拉,其他(图片、链接、选中的文字)都是可以直接拖拉的。为了让Element节点可拖拉,可以将该节点的draggable属性设为true。 ``` <div draggable="true"> 此区域可拖拉 </div> ``` draggable属性可用于任何Element节点,但是图片(img元素)和链接(a元素)不加这个属性,就可以拖拉。对于它们,用到这个属性的时候,往往是将其设为false,防止拖拉。 注意:一旦某个Element节点的draggable属性设为true,就无法再用鼠标选中该节点内部的文字或子节点了。 **10.11.1 拖放事件** - dragstart:当一个元素开始被拖拽的时候触发。用户拖拽的元素需要附加dragstart事件。在这个事件中,监听器将设置与这次拖拽相关的信息,例如拖动的数据和图像。 - dragenter:当拖拽中的鼠标第一次进入一个元素的时候触发。这个事件的监听器需要指明是否允许在这个区域释放鼠标。如果没有设置监听器,或者监听器没有进行操作,则默认不允许释放。当你想要通过类似高亮或插入标记等方式来告知用户此处可以释放,你将需要监听这个事件。 - dragover:当拖拽中的鼠标移动经过一个元素的时候触发。大多数时候,监听过程发生的操作与dragenter事件是一样的。 - dragleave:当拖拽中的鼠标离开元素时触发。监听器需要将作为可释放反馈的高亮或插入标记去除。 - drag:这个事件在拖拽源触发。即在拖拽操作中触发dragstart事件的元素。 - drop:这个事件在拖拽操作结束释放时于释放元素上触发。一个监听器用来响应接收被拖拽的数据并插入到释放之地。这个事件只有在需要时才触发。当用户取消了拖拽操作时将不触发,例如按下了Escape(ESC)按键,或鼠标在非可释放目标上释放了按键。 - dragend:拖拽源在拖拽操作结束将得到dragend事件对象,不管操作成功与否。 注意点: 拖拉过程只触发以上这些拖拉事件,尽管鼠标在移动,但是鼠标事件不会触发。 将文件从操作系统拖拉进浏览器,不会触发dragStart和dragend事件。 dragenter和dragover事件的监听函数,用来指定可以放下(drop)拖拉的数据。由于网页的大部分区域不适合作为drop的目标节点,所以这两个事件的默认设置为当前节点不允许drop。如果想要在目标节点上drop拖拉的数据,首先必须阻止这两个事件的默认行为,或者取消这两个事件。 ``` <div ondragover="return false"> <div ondragover="event.preventDefault()"> 下面是一个例子,将图片拖放到另一个div中: <div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div> <img id="drag1" src="f.jpg" draggable="true" ondragstart="drag(event)" > function allowDrop(ev) { ev.preventDefault(); } function drag(ev) { ev.dataTransfer.setData("Text",ev.target.id); } function drop(ev) { ev.preventDefault(); var data=ev.dataTransfer.getData("Text"); ev.target.appendChild(document.getElementById(data)); } ``` **10.11.2 DataTransfer对象** 所有的拖拉事件都有一个dataTransfer属性,用来保存需要传递的数据。返回一个DataTransfer对象。 拖拉的数据保存两方面的数据:数据的种类(又称格式)和数据的值。数据的种类是一个MIME字符串,比如 text/plain或者image/jpeg,数据的值是一个字符串。一般来说,如果拖拉一段文本,则数据默认就是那段文本;如果拖拉一个链接,则数据默认就是链接的URL。 当拖拉事件开始的时候,可以提供数据类型和数据值;在拖拉过程中,通过dragenter和dragover事件的监听函数,检查数据类型,以确定是否允许放下(drop)被拖拉的对象。比如,在只允许放下链接的区域,检查拖拉的数据类型是否为text/uri-list。 发生drop事件时,监听函数取出拖拉的数据,对其进行处理。 **(1)DataTransfer对象的属性** **dropEffect** dropEffect属性设置放下(drop)被拖拉节点时的效果,可能的值包括copy(复制被拖拉的节点)、move(移动被拖拉的节点)、link(创建指向被拖拉的节点的链接)、none(无法放下被拖拉的节点)。设置除此以外的值,都是无效的。 e.dataTransfer.dropEffect = 'copy'; dropEffect属性一般在dragenter和dragover事件的监听函数中设置,对于dragstart、drag、dragleave这三个事件,该属性不起作用。 **effectAllowed** effectAllowed属性设置本次拖拉中允许的效果,可能的值包括copy(复制被拖拉的节点)、move(移动被拖拉的节点)、link(创建指向被拖拉节点的链接)、copyLink(允许copy或link)、copyMove(允许copy或move)、linkMove(允许link或move)、all(允许所有效果)、none(无法放下被拖拉的节点)、uninitialized(默认值,等同于all)。如果某种效果是不允许的,用户就无法在目标节点中达成这种效果。 dragstart事件的监听函数,可以设置被拖拉节点允许的效果;dragenter和dragover事件的监听函数,可以设置目标节点允许的效果。 e.dataTransfer.effectAllowed = 'copy'; **files ** files属性是一个FileList对象,包含一组本地文件,可以用来在拖拉操作中传送。如果本次拖拉不涉及文件,则属性为空的FileList对象。 **types** types属性是一个数组,保存每一次拖拉的数据格式,比如拖拉文件,则格式信息就为File。 **(2)DataTransfer对象的方法** **setData()** setData方法用来设置事件所带有的指定类型的数据。它接受两个参数,第一个是数据类型,第二个是具体数据。如果指定的类型在现有数据中不存在,则该类型将写入types属性;如果已经存在,在该类型的现有数据将被替换。 e.dataTransfer.setData('text/plain','bb'); **getData()** getData方法接受一个字符串(表示数据类型)作为参数,返回事件所带的指定类型的数据(通常是用setData方法添加的数据)。如果指定类型的数据不存在,则返回空字符串。通常只有drop事件触发后,才能取出数据。如果取出另一个域名存放的数据,将会报错。 **clearData()** clearData方法接受一个字符串(表示数据类型)作为参数,删除事件所带的指定类型的数据。如果没有指定类型,则删除所有数据。如果指定类型不存在,则原数据不受影响。 ``` e.dataTransfer.clearData('text/plain'); ``` **setDragImage()** 拖动过程中(dragstart事件触发后),浏览器会显示一张图片跟随鼠标一起移动,表示被拖动的节点。这张图片是自动创造的,通常显示为被拖动节点的外观,不需要自己动手设置。setDragImage方法可以用来自定义这张图片,它接受三个参数,第一个是img图片元素或者canvas元素,如果省略或为null则使用被拖动的节点的外观,第二个和第三个参数为鼠标相对于该图片左上角的横坐标和右坐标。 **10.12 模拟事件** 模拟事件要经过三步: - 创建event对象 - 初始化 - 使用dispatchEvent()方法触发事件 **(1)document.createEvent()** document.createEvent方法用来新建指定类型的事件。它所生成的Event实例,可以传入dispatchEvent方法。 createEvent方法接受一个字符串作为参数,表示要创建的事件类型的字符串,可能值是: - UIEvents: 一般化的UI事件(文档事件)。鼠标事件和键盘事件都继承自UI事件。DOM3级中是UIEvent - MouseEvents:一般化的鼠标事件。DOM3中是MouseEvent - MutationEvents:一般化的DOM变动事件。DOM3中是MutationEvent - HTMLEvents:一般化的HTML事件。 **(2)event.initEvent()** 事件对象的initEvent方法,用来初始化事件对象,还能向事件对象添加属性。该方法的参数必须是一个使用Document.createEvent()生成的Event实例,而且必须在dispatchEvent方法之前调用。 initEvent方法可以接受四个参数。 - type:事件名称,格式为字符串。 - bubbles:事件是否应该冒泡,格式为布尔值。可以使用event.bubbles属性读取它的值。 - cancelable:事件是否能被取消,格式为布尔值。可以使用event.cancelable属性读取它的值。 - option:为事件对象指定额外的属性。 ``` var event = document.createEvent('MouseEvent'); event.initEvent('click',true,false); div.dispatchEvent(event); ``` 也可以使用相应的构造函数来创建event ``` var event = new MouseEvent('click', { 'bubbles': true, 'cancelable': true }); div.dispatchEvent(event); ``` **10.13 自定义事件** 我们可以使用自定义事件 ``` //新建事件实例 var event = new Event('play'); //添加监听函数 element.addEventListener('play',funciton(e){},false); //触发事件 element.dispatchEvent(event); ``` **CustomEvent()** Event构造函数只能指定事件名,不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,需要使用CustomEvent构造函数生成自定义的事件对象。 ``` var event = new CustomEvent('play',{detail: 'play'}); //添加监听函数 element.addEventListener('play',handler,false); //触发事件 element.dispatchEvent(event); ``` CustomEvent构造函数的第一个参数是事件名称,第二个参数是一个对象。在上面的代码中,该对象的detail属性会绑定在事件对象之上。 ``` funciton handler(e){ var data = e.detail; } ``` 在IE上,并不支持上面的自定义事件写法,不过,我们可以采用老式写法: ``` // 新建Event实例 var event = document.createEvent('Event'); // 事件的初始化 event.initEvent('play', true, true); // 加上监听函数 document.addEventListener('play', handler, false); // 触发事件 document.dispatchEvent(event); ```