事件流过程

<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>

当点击div后.click事件冒泡顺序:

1. div
2. body
3. html
4. document
也就是从内部不断的向父节点传递,直到传递到根节点

但是如果使用事件捕获,会出现不同的结果。它的顺序正好和前面讲的相反。

而在事件流中,真正的处理顺序是事件捕获、到达目标和事件冒泡。事件捕获时从根节点到达目标节点的过程,事件冒泡是从目标节点到根节点的过程。

事件处理程序

事件是用户或浏览器执行的动作,例如单击,单击,悬停等。事件处理程序的名字统一以on开头,例如onclick, onload等。

html事件处理程序

有些处理函数可以在html中直接指定。例如:

<input type="button" value="click me" onclick="console.log('click')"/>

这里是直接传递了一个函数字符串,也可以使用函数名,例如:

<script>
function showMessage()
{
console.log("hello world");
}
</script>
<input type="button" value="click me" onclick="showMessage()"/>

事件处理函数的作用域有了一些变化,首先有一个event变量,它保存的是event对象

<input type="button"  value="click me" onclick="console.log(event.type)"/>
//event.type = button

其次,this表示的是这个事件的目标元素,上面的例子中是input,也就是说可以使用this.value访问input的value元素

但是它会导致html和js的强耦合,难以对事件处理代码进行修改,因此不推荐这种模式。

dom进行事件处理

每个元素都有一些事件处理属性,例如

let btn = document.getElementById("myBtn");
btn.onclick = function()
{
console.log("clicked");
}
btn.onclick = null;//移除事件处理程序

另外一种方法是使用addEventListener

  • addEventListener(event_name, func, call_state):
    • event_name: 事件名,例如”click”
    • func: 事件处理函数
    • call_state: 一个布尔值,为真(默认值)则在冒泡阶段调用,否则在捕获阶段调用
let btn = document.getElementById("mybtn");
btn.addEventListener("click", ()=>{
console.log(this.id);//mybtn
}, false);
  • removeEventListener(event_name, func, call_state): 和上面参数相同,只有三个参数完全匹配才可以删除对应的eventListener,并且只有这种方式才可以删除由addEventListener加入的事件处理程序。

匿名函数创建的eventListener无法删除

btn.removeEventListener("click", ()=>{
console.log(this.id);
}, false);
//删除无效,因为两个匿名函数不同

let handler = function(){
console.log(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false);//有效

IE事件处理

IE和dom类似,只是函数不同,它是attachEvent()和detachEvent(),但是和addEventListener相比少了最后一个参数,他在IE8之前只能指定冒泡模式,并且它的事件名字需要加上on,例如

var btn = document.getElementById("mybtn");
btn.attachEvent("onclick", ()={
console.log("click");
});

事件对象

event是事件处理函数的唯一参数,他有一系列关于事件的属性和方法

DOM事件对象

不同的事件中包含的属性和方法也不一样,下面是一些共有的属性和方法

属性/方法 类型 读/写 说明
bubbles bool r 表示事件是否冒泡
cancelable bool r 表示事件默认行为是否可以被取消
defaultPrevented bool r true表示已经调用preventDefault()方法
trusted bool r true表示该事件是由浏览器生成的
type string r 触发的事件类型
currentTarget 元素 r 当前事件处理程序的元素(this)
target 元素 r 事件的目标元素,冒泡的第一个程序
detail 整数 r 一些其他信息
eventPhase 整数 r 事件所处的阶段,一共有三个阶段.1表示捕获,2表示到达目标,3表示冒泡
preventDefault() 函数 r 取消事件的默认行为
stopImmediatePropagation() 函数 r 取消后续的捕获或冒泡,并组织调用后续的事件处理程序
stopPropagation() 函数 r 取消后续的捕获或冒泡

例如单击时导航到指定url是链接的默认行为,选中复选框是点击复选框的默认行为,下面程序可以取消选中:

document.querySelector("#id-checkbox").addEventListener("click", function(event) {
document.getElementById("output-box").innerHTML += "Sorry! <code>preventDefault()</code> won't let you check this!<br>";
event.preventDefault();
}, false);

IE事件对象

IE中event对象在window.event中,因为是单线程一次只会执行一个事件处理函数因此这样做也没有问题。他也有一些共有的属性和方法

属性/方法 类型 说明
cancelBubble bool 是否在冒泡阶段
returnValue bool 可读写,设置为false 表示取消默认行为
srcElement 元素 事件目标(和DOM中的target相同)
type string 事件类型

事件类型

  • 用户界面事件(UIEvent): 与BOM交互的通用事件.
  • 焦点事件(FocusEvent): 在元素获得和失去焦点时从触发
  • 滚轮事件(WheelEvent): 使用滚轮或者起到相同功能的设备时触发
  • 键入事件(InputEvent): 输入文本触发
  • 键盘事件(KeyboardEvent):
  • 合成事件(CompositionEvent): 间接输入文本(使用输入法)时产生的事件

UIEvent

UIEvent包括很多种事件,他不一定和用户操作有关,有些在DOM出现之前就已经存在了,保留它主要是为了向后兼容。

  • load: 在window上页面加载完成之后触发,在<frameset>的所有<frame>加载完成后触发,在<img>图片加载完成后触发, <object>相应对象加载完成后触发
  • unload: 当window界面完全卸载后触发,所有<frame>卸载后触发,<object>卸载完成后触发
  • abort: <object>对象在加载完成前被用户终止下载触发
  • error: 报错时触发
  • select: 文本框(<input><textarea>)上被选择若干个字符触发
  • resize: 窗口被缩放时触发
  • scroll: 滚动时在被滚动的元素上出发

FocusEvent

  • blur: 失去焦点时触发,这个事件不冒泡
  • focus: 获得焦点时触发,不冒泡
  • DOMFocusIn: 获得焦点时触发,这个事件会冒泡,另外现在主要是focusin
  • DOMFocusOut: 和上面一样,推荐使用focusout

鼠标和滚轮事件

鼠标事件主要是点击,双击等,总共有9种鼠标事件。

  • click: 用户单击左键或回车键时触发。
  • dblclick: 双击
  • mousedown: 按下任意鼠标按键触发
  • mouseenter: 从元素外部到元素内部时触发,这个事件不冒泡,也不会在移动到该元素的子元素时触发
  • mouseleave: 元素内部到元素外部触发,…
  • mousemove: 在元素内移动时反复触发
  • mouseout: 从元素移动到其他元素时触发,会冒泡并且移动到子元素也会触发
  • mouseover: 移动到元素内触发, 和上面一样
  • mouseup: 释放鼠标键触发
  • mousewheel: 鼠标滚轮滚动事件,除了共有的属性外还有一个wheelDelta的属性。鼠标向外滚动时为120,向内部滚动时为-120.

几个类似事件的触发也是有先后顺序的,mousedown和mouseup都触发完毕之后才会触发click事件。连续两次click才会有dblclick触发:

  1. mousedown
  2. mouseup
  3. click
  4. mousedown
  5. mouseup
  6. click
  7. dblclick

坐标

客户端坐标是相对于窗口的坐标,可以通过event.clientX和event.clientY获取。

页面坐标是相对于这个页面来说的,滚动页面有一部分是不可见的,页面坐标就是包括这部分不可见的计算从该点到左上角的距离。它通过pageX和pageY获取

屏幕坐标是相对于屏幕左上角,可以通过screenX和screenY获取

右键菜单js事件1.png

为了自定义右键菜单,出现了contextmenu事件,她会在右键菜单命令出现时触发。可以通过preventDefault()阻止默认的右键菜单。

<!DOCTYPE html>
<html>
<head>
<title>example</title>
</head>
<body>
<div id="myidv">right click</div>
<ul id="mymenu" style="position:absolute;visibility:hidden;background-color:silver">//一开始是隐藏的
<li><a href="http://www.somewhere.com">somewhere</a></li>
</ul>
</body>
<html>

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";
});
});

修饰键

有时候鼠标还需要根据ctrl,alt等修饰键判断状态,在js中定义了四个键属性:shiftKey,ctrlKey,altKey,metaKey。按下则为true,没有按下则为false

let div = document.getElementById("mydiv");
div.addEventListener("click", (event)=>
{
if(event.shiftKey)
{
console.log("enter shift");
}
}

相关元素

对于mouseout、mouseover等事件,除了关心事件的触发者,还关心现在移动到了哪个元素,也就是相关元素。它的属性是event.relatedTarget

对于mouseout事件来说,触发者是失去光标的元素,相关元素是获得光标的元素。

其他属性

  • event.button: 表示现在按下的是哪个按键
    • 0: 没有按下任何键
    • 1: 按下左键(主键)
    • 2: 按下右键(副键)
    • 3: 同时按下左右键
    • 4: 按下中键
    • 5: 同时按下主键和中键
    • 6: 按下副键和中键
    • 7: 同时按下三个键
  • altleft: 是否按下左边的ctrl
  • ctrlleft:
  • shiftleft:
  • offsetX: 光标相对于元素边界的x坐标
  • offsetY:

键盘和输入事件

事件:

  • keydown: 按下按键触发
  • keyup: 释放按键触发

对于字符类按键(a,b,c等),keydown和keyup会重复触发。对于非字符键(ctrl,alt等)如果只按一下,会触发keydown和keyup。如果持续按下,会一直触发keydown,待释放再触发keyup。

属性:

  • keyCode: 键码,用来表示按下了哪个按键。
  • charcode: 只有在keydown时才会被设置值,是按键对应的ascii值。
  • keycode: 和charcode相同
  • key: 返回按键的字符串,如果是非字符按键则返回按键名(Shift,ArrowDown)
  • getModifierState(name): 获得修饰键状态,例如:event.getModifierState("Shift");

textInput

textInput在输入字符时触发,他有一些属性和方法:

  • data: 输入的字符
  • inputMethod: 输入方法
    • 0: 不确定
    • 1: 表示键盘
    • 2: 粘贴
    • 3: 拖放
    • 4 : 输入法
    • 5: 表单
    • 6: 手写
    • 7: 语音
    • 8: 组合
    • 9: 脚本

合成事件

在使用输入法输入时,如果是一些其他语言(如中文),往往若干个输入才可以合成一个词,这时就需要一些新的事件进行管理

  • compositionstart: 打开时触发,表示输入即将开始
  • compositionupdate: 输入字段触发
  • compositionend: 关闭时触发,包含本次合成中输入的全部内容

html事件

beforeunload

这个事件在window上触发,它可以在用户退出前提供一个对话框询问是否退出。一般在表单填上后退出时进行提示。可以通过returnValue修改提示内容

window.addEventListener("beforeunload", (event)=>{
let message = "hello world";
event.returnValue = message;
return message;
});

DOMContentLoaded

load事件会在图片等全部加载完成后才会触发,所需时间比较长。而DOMContentLoaded会在DOM树构建完成之后触发,不用等待图片,js,css等文件加载完成。它在document或window上。

hashchange

它会在url散列值(#后面的部分)触发时通知开发者。他有两个新属性,oldURL和newURL,分别存储变化前后的url。它在window对象中。

模拟事件

可以使用document.createEvent()创建一个event对象。然后使用dispatchEvent(event)发送给某一个对象,之后就会冒泡执行。

  • document.createEvent(name): 创建一个事件,name是事件的类型,有”UIEvents”、”MouseEvents”、”HTMLEvents”
  • obj.dispatchEvents(event): 将这个事件传给obj对象并冒泡执行

模拟鼠标事件

需要新建一个鼠标event对象,然后对其初始化。初始化使用initMouseEvent()对象,它共有15个参数

  • type: 要触发的事件类型,如”click”
  • bubbles: 是否冒泡
  • cancelable: 是否可以取消
  • view:始终为document.defaultView
  • detail: 额外信息,被事件处理程序使用,通常为0
  • screenX:
  • screenY:
  • clientX:
  • clientY:
  • ctrlkey
  • altKey
  • shiftKey
  • metaKey
  • button: 表示按下了那个按钮,默认为0
  • relatedTarget: 关联对象
let btn = document.getElementById("mybtn");
let event = document.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0,
0, false, false, false, false, 0, null);
btn.dispatchEvent(event);

模拟键盘事件

和鼠标事件类似,只是参数有所不同,参数可参看