Web
浏览器中可以发生很多事件,所发生的事件类型
决定了事件对象
中会保存什么信息。
DOM3 Events
在DOM2 Events
基础上重新定义了事件,并增加了新的事件类型。所有主流浏览器都支持这两种。DOM3 Events
定义了如下事件类型:用户界面事件,焦点事件,鼠标事件,滚轮事件,输入事件,键盘事件,合成事件
。
HTML5
还定义了另一组事件,而浏览器通常在DOM
和BOM
上实现专有事件,这些专有事件基本上都是根据开发需求而不是规范新增中,因此不同浏览器的实现可能不同。
17.4.1用户界面事件
UIEvent
涉及与BOM
交互的通用浏览器事件
1. load
事件
-
在
window
对象上,load事件会在整个页面(图片,js
文件,css
文件)加载完成后触发,可通过两种
方式指定load事件处理程序:JavaScript
方式(推荐)
<script> window.addEventListener("load",(event)=>{ console.log("页面加载完成"); }) // 页面加载完成 </script>
- 向
<body>
元素添加onload
属性
<body onload="console.log('页面加载完成')">
-
在
图片
上也会触发load事件,可直接给<img>
元素的onload
属性指定事件处理程序。在通过JavaScript
创建<img>
元素时,也可以给这个元素指定一个加载完成后执行的事件处理程序,关键是要在赋值src属性前指定事件处理程序
<script> window.addEventListener("load",()=>{ let image = document.createElement("img"); image.addEventListener("load",(event)=>{ console.log(event.target.src); }); document.body.appendChild(image); image.src="1.gif" }); // http://127.0.0.1:5500/html/1.gif </script>
这个例子首先为window指定了一个load事件处理程序,因为涉及向
DOM
中添加新元素,所以必须确保页面已经加载完成,如果在页面加载完成之前操作document.body
,则会导致错误。然后,代码创建了一个新的<img>
元素,并为这个元素设置了load事件处理程序,最后,才把这个元素添加到文档中并指定了其src
属性。下载图片不一定要把<img>
元素添加到文档中,只要给它设置了src属性就会立即开始下载。 -
<script>
元素会在js
文件加载完成后触发load
事件,从而可以动态检测。与图片不同,下载js
文件必须同时指定src
属性,并把<script>
元素添加到文档中。IE8
及更早版本不支持<script>
元素触发load事件。<script> window.addEventListener("load",()=>{ let script = document.createElement("script"); script.addEventListener("load",(event)=>{ console.log("loaded"); }); script.src = "example.js" document.body.appendChild(script); }) // i am example.js // loaded </script>
2.unload
事件
-
unload
事件会在文档卸载完成后触发,一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以避免内存泄漏。两种按指定方式:js
方式
<script> window.addEventListener("unload",(event)=>{ console.log("unloaded!") }) </script>
- 给
<body>
元素添加onload
属性
<body onunload="console.log('unloaded!')">
无论使用何种方式,都要注意事件处理程序中的代码,因为
unload
事件是在页面卸载完成后触发的,所以不能使用页面加载后才有的对象,此时要访问DOM
或修改外观都会导致错误。
3.resize
事件
-
当浏览器窗口被缩放到新高度或宽度时,会触发
resize
事件,这个事件在window上触发,因此可以通过js
在window
上或者为<body>
元素添加onresize
属性来指定事件处理程序。优先使用js
方式。<script> window.addEventListener("resize",(event)=>{ console.log("resized"); }) </script>
-
不同浏览器在决定何时触发
resize
事件上存在重要差异,IE Safari Chrome Opera
会在窗口缩放超过1像素时触发resize
事件,然后随着用户缩放窗口不断触发。Firefox
早期版本则只在用户停止缩放浏览器窗口时触发resize
事件,无论如何,都应该避免这个事件处理程序中执行过多计算,否则可能由于执行过于频繁而导致浏览器响应变慢。
4.scroll
事件
-
虽然scroll事件发生在
window
上,但实际反应的是页面中相应元素的变化,在混杂模式下,可以通过<body>
元素检测scrollLeft 和 scrollTop
属性的变化。在标准模式下,这些变化在除早期版的Safari
之外的浏览器中都发生在<html>
元素上。(早期版的Safari
在<body>
上跟踪滚动位置)。<script> window.addEventListener("scroll",(event)=>{ // 标准模式下 if(document.compatMode == "CSS1Compat"){ console.log(document.documentElement.scrollTop); console.log(event); // 混杂模式下 }else{ console.log(document.body.scrollTop); } }) </script>
17.4.2焦点事件
- 焦点事件在页面元素获得或失去焦点时触发,这些事件可以与
document.hasFocus() 和 document.activeElement
一起为开发者提供用户在页面中导航的信息。 - 分类:
focus
:当元素获得焦点时触发,不冒泡,所有浏览器都支持。blur
:当元素失去焦点时触发,不冒泡,所有浏览器都支持。focusin
:当元素获得焦点时触发,冒泡focusout
:当元素失去焦点进触发,冒泡
17.4.3鼠标和滚轮事件
鼠标是用户的主要定位设备,DOM3 Events
定义了9
种鼠标事件:
click
:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发,这主要是基于无障碍的考虑,让键盘和鼠标都可以触发onclick
事件处理程序。dblclick
:在用户双击鼠标左键时触发。mousedown
:在用户按下任意鼠标键时触发。mouseenter
:在用户把鼠标光标从元素外部移到元素内部时触发,不冒泡。mouseleave
:在用户把鼠标光标从元素内部移到元素外部时触发,不冒泡。mousemove
:在鼠标光标在元素上移动时反复触发。mouseout
:在用户把鼠标光标从一个元素移到另一个元素时触发,移到的元素可以是原始元素的外部元素
,也可以是原始元素的子元素
。mouseover
:在用户把鼠标光标从元素外部移到元素内部时触发。mouseup
:在用户释放鼠标键时触发。mousewheel
:鼠标滚轮事件
页面中所有的元素都支持鼠标事件,除了mouseenter
和mouseleave
外,所有鼠标事件都会冒泡,都可以被取消,而这会影响浏览器的默认行为。
比如:click
事件触发的前提是mousedown
事件触发后,紧接着又在同一个元素上触发了mouseup
事件,如果mousedown
和mouseup
中的任意一个事件被取消,那么click
事件就不会触发了。
1.客户端坐标
鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在event
对象的clientX 、clienY
属性中。这两个属性表示事件发生时鼠标光标在视口中的坐标
,所有浏览器都支持。
<div id="myDiv">获取鼠标事件的客户端坐标</div> <script> let div = document.getElementById("myDiv"); div.addEventListener("click",(event)=>{ console.log(`client coordinates:${event.clientX},${event.clientY}`); }) </script>
2.页面坐标
页面坐标是事件发生时鼠标光标在页面上的坐标,通过event
对象的pageX、 pageY
可以获取,这两个属性表示鼠标光标在页面上的位置,因此反映的是光标到页面而非视口左边与上边的距离。
<div id="myDiv">获取鼠标事件的页面坐标</div> <script> let div = document.getElementById("myDiv"); div.addEventListener("click",(event)=>{ console.log(event.pageX,event.pageY); }) </script>
在页面没有滚动时,pageX 和pageY
与clientX 和clientY
的值相同。
IE8
及更早版本没有在event
对象上暴露出页面坐标,不过可以通过客户端坐标和滚动信息计算出页面坐标。滚动信息可以从document.body(混杂模式)
或document.documentElement(标准模式)
的scrollLeft
和scrollTop
属性获取。
<div id="div">获取页面坐标</div> <script> let mydiv = document.getElementById("div"); mydiv.addEventListener("click",(event)=>{ let pageX = event.pageX; let pageY = event.pageY; if(pageX == undefined){ pageX = clientX + (document.body.scrollLeft || document.documentElement.scrollLeft); } if(pageY == undefined){ pageY = clientY + (document.body.scrollTop || document.documentElement.scrollTop); } console.log(pageX,pageY); }) </script>
3.屏幕坐标
通过event
对象的screenX
和screenY
属性获取鼠标光标在屏幕上的坐标。
<div id="d1">获取屏幕坐标</div> <script> let d1 =document.getElementById("d1"); d1.addEventListener("click",(event)=>{ console.log(event.screenX,event.screenY); }) </script>
4.修饰键
-
虽然鼠标事件主要是通过鼠标触发的,但有时候要确定用户想实现的操作,还要考虑键盘按键的状态。键盘上的修饰键
shift 、 Ctrl 、 Alt 、 Meta
经常用于修改鼠标事件的行为。DOM
中规定了4个属性来表示这几个修改键的状态:shiftKey 、ctrlKey、 altKey、 metaKey
。这几个会在各自对应的修改键被按下时包含布尔值true,没有被按下时包含布尔值false。在鼠标事件发生时可以通过这几个属性来检测修改键是否被按下。<div id="d2">测试修饰键</div> <script> let d2 =document.getElementById("d2"); d2.addEventListener("click",(event)=>{ let keys = []; if(event.shiftKey){ keys.push("shift"); } if(event.ctrlKey){ keys.push("ctrl"); } if(event.altKey){ keys.push("alt"); } if(event.metaKey){ keys.push("meta"); } console.log(keys.join()); }) </script>
注:
在 Mac 系统键盘上,meta 对应
command 键
(⌘)。在 Windows 系统键盘 meta 对应
Windows 徽标键
(⊞)。在 Sun 操作系统键盘上,meta 对应
实心宝石键
(◆)。
5.相关元素
对mouseover
和mouseout
事件而言,还存在与事件相关的其它元素。这两个事件都涉及从一个元素的边界之内把光标移到另一个元素的边界之内。DOM
通过event
对象的relatedTarget
属性提供了相关元素的信息。若是除mouseover
和mouseout
外的其它事件,这个属性的值都是null
。
<div id="blue" style="background-color: blue;width: 200px; height: 200px; position: relative;"> <div id="red" style="background-color: red; margin-top: -50px; margin-left: -50px; width: 100px; height: 100px; left: 50%; top:50%; position: absolute;"></div> </div> <script> let blueDiv = document.getElementById("blue"); blueDiv.addEventListener("mouseover",(event)=>{ console.log(111); console.log(event.relatedTarget); console.log(222); }) </script>
6.鼠标按键
对mousedown
和mouseup
事件来说,event对象上会有一个button
属性,表示按下或释放的是哪个按键。DOM为这个button
属性定义了3个值:0
代表鼠标主键,1
表示鼠标中键(通常也是滚轮键),2
表示鼠标副键。
<div id="d3">鼠标按键</div> <script> let d3 = document.getElementById("d3"); d3.addEventListener("mousedown",(event)=>{ console.log(event.button); }) </script>
7.额外事件信息
DOM2 Events
规范在event
对象上提供了detail
属性,以给出关于事件的更多信息。对鼠标事件来说,detail
包含一个数值,表示在给定位置上发生了多少次点击。单击
相当于在同一个像素上发生一次mousedown紧跟一次mouseup
。detail的值从1
开始,每次单击会加1
,如果鼠标在mousedown
和mouseup
之间移动
了,则detail
会重置为0
。
<div id="d4">额外事件信息</div> <div id="d5">额外事件信息2</div> <script> // 鼠标按下并抬起之后才会打印出event.detail let d4 = document.getElementById("d4"); d4.addEventListener("click",(event)=>{ console.log(event.detail); }); // 鼠标按下之后即打印出event.detail let d5 = document.getElementById("d5"); d5.addEventListener("mousedown",(event)=>{ console.log(event.detail); }) </script>
8.mousewheel
事件
mousewheel
事件会在用户使用鼠标滚轮时触发,包括在垂直方向上任意滚动。这个事件会在任何元素上触发,并冒泡到window
上,mousewheel
事件的event
对象包含鼠标事件的所有标准信息,此外还有一个名为wheelDelta
的新属性。当鼠标滚轮向前时,wheelDelta
每次都是+120
;当鼠标滚轮向后时,wheelDelta
每次都是-120
。
可以为页面上的任何元素可文档添加onmousewheel
事件处理程序,以处理所有鼠标滚轮交互。
<script> // 书上说每次滚动±120,目前自测是±150 document.addEventListener("mousewheel",(event)=>{ console.log(event.wheelDelta); }) </script>
9.触摸屏设备
在为IOS
和Android
等触摸屏开发时需注意:
- 不支持
dblclick
事件,双击浏览器窗口可以放大,但没有办法覆盖这个行为。 - 单指点触屏幕上的可点击元素会触发
mousemove
事件,如果操作会导致内容变化,则不会再触发其它事件。如果屏幕上没有变化,则会相继触发mousedown、mouseup 、click
事件,点触不可点击的元素不会触发事件。 mousemove
事件也会触发mouseover
和mouseout
事件。- 双指点触屏幕并滑动导致页面滚动时会触发
mousewheel
和scroll
事件。
10.无障碍问题
- 使用
click
事件执行代码。
17.4.4键盘与输入事件
键盘事件是用户操作键盘时触发的。包含3个事件:
keydown
:用户按下键盘上某个键时触发,而且持续按住会重复触发
。keypress
:用户释放键盘上某个键时触发。textInput
:是对keypress
事件的扩展,用于在文本显示给用户之前更方便的截获文本输入。textInput
会在文本被插入到文本框之前触发。
1.键码
对于keydown
和keyup
事件,event对象的keyCode
属性中保存一个键码,对应键盘上特定的一个键。对于 字母和数字键,keyCode
的值与小写字母和数字的ASCII
编码一致。而且跟是否按了shift
键无关。
<input type="text" name="" id="myText"> <script> let textbox = document.getElementById("myText"); textbox.addEventListener("keyup",(event)=>{ console.log(event.keyCode); }) </script>
2.textInput
事件
DOM3 Events
规范新增textInput
事件,其在字符被输入到可编辑区域时触发。
与keypress
的区别:
keypress
会在任何可以获得焦点的元素上触发,而textIput
只在可编辑区域触发。keypress
对任何可能影响文本的键都会触发,而textInput
只有在新字符被插入时才会触发。
因为textInput
事件关注字符,所以在event
对象上提供了一个data
属性,包含要插入的字符。data
的值始终是要被插入的字符,因此如果在按S
键时没有按shift
,data
的值就是s
;如果按S
键时按shift
,data
的值就是S
。
<input type="text" name="" id="text"> <script> let text = document.getElementById("text"); text.addEventListener("textInput",(event)=>{ console.log(event.data); }) </script>
17.4.5HTML5
事件
DOM
规范并未涵盖浏览器都支持的所有事件,很多浏览器根据特定的用户需求或使用场景实现了自定义事件,HTML5
详尽的列出了浏览器支持的所有事件,以下只是得到浏览器较好支持的一些事件。
1.contextmenu
事件
- 专门用于表示何时该显示上下文菜单,从而允许开发者取消默认的上下菜单并提供自定义菜单。
contextmenu
事件冒泡,因此只要给document
指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。这个事件在所有浏览器中都可以取消,在DOM
合规的浏览器中使用event.preventDefault()
。contextmenu
事件应该算是一种鼠标事件,因此event
对象上的很多属性都与光标位置有关。通常,自定义的上下文菜单都是通过这个事件处理程序来触发的显示,并通过onclick
事件处理程序触发隐藏的。
<div id="myDiv">Right click</div> <ul id="myMenu" style="position: absolute; visibility: hidden; background-color: antiquewhite;"> <li><a href="http://www.baidu.com">百度</a></li> <li><a href="http://www.baidu.com">百度</a></li> <li><a href="http://www.baidu.com">百度</a></li> </ul> <script> window.addEventListener("load", (event) => { let div = document.getElementById("myDiv"); div.addEventListener("contextmenu", (event) => { // 阻止默认事件 event.preventDefault(); let menu = document.getElementById("myMenu"); menu.style.left = event.clientX + "px"; menu.style.top = event.clientY + "px"; menu.style.visibility = "visible"; }) document.addEventListener("click", (event) => { document.getElementById("myMenu").style.visibility = "hidden"; }) }) </script>
这里在<div>
元素上指定了一个contextmenu
事件处理程序,这个事件处理程序先取消默认行为,确保不会显示浏览器默认的上下文菜单,接着基于event
对象的clientX、clientY
属性把<ul>
元素放在适当的位置。最后一步,通过将visibility
属性设置为visible
让自定义上下文菜单显示出来。另外,又给document
添加了一个onclick
事件处理程序,以便在单击事件发生时隐藏上下文菜单。
2.beforeunload
事件
beforeunload
事件会在window
上触发,用意是给开发者提供阻止页面被卸载的机会,这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。这个事件不能取消,否则就意味着可以把用户永久阻拦在一个页面上。相反,这个事件会向用户显示一个确认框,其中的消息表明浏览器即将卸载页面,并请用户确认是希望关闭页面,还是继续留在页面上。
<script> window.addEventListener("beforeunload",(event)=>{ let message = "I'm really going to miss you if you go."; event.returnValue = message; return message; }) </script>
3.DOMContentLoaded
事件
-
此事件会在
DOM
树构建完成后立即触发,而不用等待图片,js
文件 、css
文件或其它资源加载完成。相对于load
事件,可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快的与页面交互。 -
要处理
DOMContentLoaded
事件,需要给document
或window
添加事件处理程序。 -
对于不支持此事件的浏览器,可以使用超时为0的
setTimeout()
函数,通过其回调来设置事件处理程序。<script> document.addEventListener("DOMContentLoaded",(event)=>{ console.log(event); console.log("content loaded"); }) </script>
不支持此事件时可以设置定时器。
<script> setTimeout(() => { // 在此处添加事件处理程序 console.log(111); }, 0); </script>
4.readystatechange
事件
-
此事件提供文档或元素加载状态的信息,但行为有时候并不稳定,支持此事件的每个对象都有一个
readyState
属性,该属性具有以下可能的字符串值:- uninitialized:对象存在尚未初始化
- loading:对象正在加载数据
- loaded:对象已经加载完数据
- interactive:对象可以交互,但尚未加载完成
- complete:对象加载完成
但并非所有对象都会经历
readystate
阶段,文档中也并未说明哪些阶段适用于哪些对象,这意味着此事件经常触发不到4次,而且readystate
未必会依次呈现上述值。 -
在
document
上使用时,值为interactive
的readyState
首先会触发此事件,时机类似于DOMContentLoaded
,进入交互阶段,意味着DOM
树已加载完成,因而可以安全的交互了。<script> document.addEventListener("readystatechange",(event)=>{ if(document.readyState == "interactive"){ console.log("content loaded"); } }) </script>
-
交互阶段与完成阶段的顺序也不是固定的。在外部资源较多的页面中,很可能交互阶段早于完成阶段,而在外部资源较少的页面,很可能完成阶段会早于交互阶段。因此实践中为了抢到较早的时机,需要同时检测交互阶段和完成阶段。
<script> document.addEventListener("readystatechange",(event)=>{ if(document.readyState == "interactive" || document.readyState == "complete"){ console.log("content loaded"); } }) </script>
5.haschange
事件
-
用于在
url
散列值(url最后#后面的部分)发生变化时通知开发者。这是因为开发者经常在AJAX
应用程序中使用URL
散列值存储状态信息或路由导航信息。 -
onhaschange
事件处理程序必须添加给window
,每次URL
散列值发生变化时会调用它。event对象有两个新属性:oldURL 和 newURL
,这两个属性分别保存变化前后的URL
,而且是包含散列值的完整的URL
。<script> window.addEventListener("hashchange",(event)=>{ console.log(event.oldURL); console.log(event.newURL); }) window.addEventListener("hashchange",(event)=>{ console.log(location.hash); }) </script>