# EventTarget
# 构造
EventTarget
- 作用:构造一个事件监听器
- 调用:new EventTarget()
- 增加监听:target.addEventListener()
- 触发事件:target.dispatchEvent(new Event())
使用
手写EventTarget
- 为多个元素、多种事件类型绑定同一个事件触发函数
<body> <button>按钮1</button> <button>按钮2</button> <div style="background-color: thistle;">盒子1</div> <div style="background-color: rgb(191, 196, 216);">盒子2</div> <script> const btns = document.getElementsByTagName('button'); const divs = document.getElementsByTagName('div'); const targetSet = new Set(); Array.prototype.forEach.call(btns, item => { targetSet.add(item); }) Array.prototype.forEach.call(divs, item => { targetSet.add(item); }) const target = new EventTarget(); target.addEventListener('go', () => console.log('你点击或者 mousemove 了div或者btn')); document.body.addEventListener('click', e => { if (targetSet.has(e.target)) { target.dispatchEvent(new Event('go')); } }) document.body.addEventListener('mousemove', e => { if (targetSet.has(e.target)) { target.dispatchEvent(new Event('go')); } }) </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# addEventListener
- 作用:为文档上的元素绑定事件监听,触发函数
- 调用:element.addEventlistener(event, callback[, option | useCapture])
- 入参:String, Function[, Object | boolean]
- 返回:undefined
- tip:文档上的元素指Element/Window/Document/XMLHttpRequest/其他支持事件的对象
- DOM上onclick类的事件绑定都是以属性的形式存在DOM对象上,因此不能重复绑定
- addEventListener可以同时绑定多个事件,因为注册的是一个事件列表
onlick
addEventListener
第三个参数
事件捕获
手写简易版
<body> <button class="btn1">按钮1</button> <script> const btn1 = document.getElementsByClassName("btn1")[0]; // 会被覆盖 btn1.onclick = () => console.log(11); btn1.onclick = () => console.log(22); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# removeEventListener
- 作用:移除监听事件
- 调用:eventTarget.removeEventTarget(type, callback, options)
- 入参:String, Function[, Boolean | Object]
- 返回:undefined
<body> <button id='btn'>按钮</button> <script> const btn = document.querySelector('#btn'); const callback1 = () => console.log('1'); const callback2 = () => console.log('2'); btn.addEventListener('click', callback1); btn.addEventListener('click', callback2); btn.click(); // 1 2 setTimeout(() => { console.log('移除事件1,再点击'); btn.removeEventListener('click', callback1); btn.click(); // 2 }, 2000) </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# dispatchEvent
- 作用:向指定目标派发事件
- 调用:element.dispatchEvent(event)
- 入参:Event
- 返回:undefined
只发射一次
- 每个事件只能被发射一次,多次发射报错
使用
捕获/冒泡
hideBox
多次发射
- 自定义事件设置及触发
<body> <button id='btn'>按钮</button> <script> const btn = document.querySelector('#btn'); btn.addEventListener('go', () => console.log('go go go!')); // btn.go(); // error setTimeout(() => { const goEvent = new Event('go'); btn.dispatchEvent(goEvent); // go go go! }, 2000) </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Event属性
# Event
构造
- 作用:构造新的Event事件
- 调用:new Event(type[, {bubbles, cancelable, composed}])
- 入参:String[, {Boolean, Boolean, Boolean}]
使用
冒泡事件
<body> <div> <button>按钮1</button> </div> <script> const div = document.querySelector('div'); const btn = document.querySelector('button'); btn.addEventListener('say', () => console.log('btn说我被触发了')); div.addEventListener('say', () => console.log('div说我被触发了')); const event = new Event('say'); // 发射事件,但并没有冒泡 btn.dispatchEvent(event); // btn说我被触发了 </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# target
- 值:事件发起节点对象
- 获取:e.target
# currentTarget
- 作用:拿到事件当前传播到的node对象
- 获取:e.currentTarget
<body> <div> <button>按钮</button> </div> <script> const button = document.querySelector('button'); const div = document.querySelector('div'); div.addEventListener('click', e => { console.log(e.target); // <button>按钮</button> console.log(e.currentTarget); // <div>...</div> }) button.click(); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# type
- 值:事件的类型
- 返回:String
<body> <button>按钮</button> <script> const button = document.querySelector('button'); button.addEventListener('go', function(e) { console.log(e.type); // go }); const e = new Event('go'); button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# bubbles
- 作用:检测事件是否会冒泡
- 获取:event.bubbles
- 值:Boolean
<body> <div> <button>按钮1</button> </div> <script> const div = document.querySelector('div'); const btn = document.querySelector('button'); btn.addEventListener('click', e => console.log(e.bubbles)); // div盒子在冒泡阶段等click事件触发 div.addEventListener('click', () => console.log('div说我被触发了'), false); // new Event默认不冒泡 btn.dispatchEvent(new Event('click')); // false console.log('等两秒'); // true div说我被触发了 setTimeout(() => btn.dispatchEvent(new Event('click', {bubbles: true})), 2000) </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# cancelable
- 作用:设置事件默认事件是否可被preventDefault()取消
- 获取:event.cancelable
- 值:Boolean
- tip:正常传播,只是禁止了默认事件
# defaultPrevented
- 作用:查看该事件是否调用了preventDefault方法
- 调用:e.defaultPrevented
- 返回:Boolean
- dispatchEvent发射的事件好像不生效?
- 手动触发可以
- 点击按钮,显示true
<body> <button>按钮</button> <script> const button = document.querySelector('button'); button.addEventListener('click', function(e) { e.preventDefault(); console.log(e.defaultPrevented); // true }) // 生成的事件无效? // const e = new Event('click'); // e.preventDefault(); // button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# eventPhase
- 作用:查看当前事件传播到哪个阶段了
- 调用:event.eventPhase
- 返回:Number
| 值 | 含义 |
|---|---|
| 0 | 事件没开始传播 |
| 1 | 事件在捕获阶段 |
| 2 | 事件到达target |
| 3 | 事件在冒泡阶段 |
<body> <div> <button>按钮</button> </div> <script> const button = document.querySelector('button'); const div = document.querySelector('div'); div.addEventListener('click', function(e) { console.log('div捕获:' + e.eventPhase); // 1 }, true); button.addEventListener('click', function(e) { console.log('button捕获:' + e.eventPhase); // 2 }, true); button.addEventListener('click', function(e) { console.log('button冒泡:' + e.eventPhase); // 2 }); div.addEventListener('click', function(e) { console.log('div冒泡:' + e.eventPhase); // 3 }); const e = new Event('click'); console.log(e.eventPhase); // 0 button.dispatchEvent(e); setTimeout(() => console.log('结束' + e.eventPhase)); // 0 </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# isTrusted
- 作用:查看该事件是不是系统信任的浏览器发起事件【用户生成的返回false】
- 调用:event.isTrusted
- 返回:Boolean
- tip:只读属性
- dispatchEvent发起的值是false,点击时浏览器发起的值是true
<body> <button>按钮</button> <script> const button = document.querySelector('button'); button.addEventListener('click', function(e) { console.log(e.isTrusted); // false }); const e = new Event('click'); button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Event方法
# preventDefault
- 作用:阻止浏览器默认事件作用触发
- 调用:e.preventDefault()
- tip:不影响自定义事件,不影响事件传播
# composedPath
- 作用:事件的传播路径
- 调用:e.composedPath()
- 返回:Array【栈结构】
返回值
- 0是target,最下面是window
- 从一开始就把路径存完了,并不是边执行边存的
<body> <article> <div> <button>按钮</button> </div> </article> <script> const button = document.querySelector('button'); const div = document.querySelector('div'); button.addEventListener('click', function(e) { console.log(e.composedPath()); // Array(7) [button, div ...] }); div.addEventListener('click', function(e) { console.log(e.composedPath()); // Array(7) [button, div ...] },true); const e = new Event('click'); button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# stopImmediatePropagation
stop stopImmediatePropagation
- 翻译:停止立即传播
- 作用:不再调用其他的事件回调,并且阻止传播
- 调用:e.stopImmediatePropagation()
- 返回:undefined
- addEventListener同一个事件时是按序执行,stopImmediatePropagation之前的都执行,之后的都不执行
- 并且停止事件的传播
<body> <article> <div> <button>按钮</button> </div> </article> <script> const button = document.querySelector('button'); const div = document.querySelector('div'); button.addEventListener('click', function(e) { console.log('e的点击事件1'); }); button.addEventListener('click', function(e) { console.log('e的点击事件2'); e.stopImmediatePropagation(); }); // 已被阻止剩下的事件回调执行,无 button.addEventListener('click', function(e) { console.log('e的点击事件3'); }); // 已被停止事件传播,无 div.addEventListener('click', function(e) { console.log('到div了'); }); const e = new Event('click'); button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# stopPropagation
- 作用:停止事件传播,但本对象上的监听回调会执行完
- 调用:e.stopPropagation()
<body> <article> <div> <button>按钮</button> </div> </article> <script> const button = document.querySelector('button'); const div = document.querySelector('div'); button.addEventListener('click', function(e) { console.log('e的点击事件1'); }); button.addEventListener('click', function(e) { console.log('e的点击事件2'); e.stopPropagation(); }); // 会执行完本对象的事件回调 button.addEventListener('click', function(e) { console.log('e的点击事件3'); }); // 已被停止事件传播,无 div.addEventListener('click', function(e) { console.log('到div了'); }); const e = new Event('click'); button.dispatchEvent(e); </script> </body>复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 移动端事件
移动端
示例
点击穿透
| type | 场景 |
|---|---|
| touchstart | 触摸交互,tab切换,链接跳转 |
| touchmove | 画板、拖动、滑动特效、签名 |
| touchend | 主要跟touchmove结合使用:滑动结束 |
| touchcancel | 很少使用,touch被打断时触发:(电量不足) |
如果没开理想视口,click延迟会在300ms左右,开启理想视口会快一些。尽量在移动端使用 touchstart / touchend
- 触发顺序:touchstart => (touchmove) => touchend =>
click(捕获冒泡都在这儿开始)
touchmove手不离开屏幕会一直触发,即使离开了监听区域
# clientX/pageX/screenX
e.targetTouches[0].clientX是相对于浏览器左边缘距离e.targetTouches[0].screenX是相对于屏幕左边缘距离e.targetTouches[0].pageX是相对于页面左边缘距离,有可能发生了滚动
# touch事件和mouse事件打平
- 鼠标
mousemove和touch事件取x和y方法不一样 - mouse有移出屏幕的风险,会导致监听触发不符合预期
- mouse事件有x,y,touch事件只有clientX,clientY
- touch事件必须拿
targetTouches或touches获取clientX/clientY - mac的触控板是touch事件
// 打平移动端位置事件 const moveListener = (el, callback) => { let mouseDown = false; const touchMove = e => callback(e.targetTouches[0].clientX, e.targetTouches[0].clientY); const mouseDownEvent = () => mouseDown = true; const mouseUp = () => mouseDown = false; const mouseOut = () => mouseDown = false; const mouseMove = (e) => { if (!mouseDown) return; callback(e.clientX, e.clientY); } el.addEventListener("touchmove", touchMove); el.addEventListener("mousedown", mouseDownEvent); el.addEventListener("mouseup", mouseUp); el.addEventListener("mouseout", mouseOut); document.addEventListener("mouseout", mouseOut); el.addEventListener("mousemove", mouseMove); return () => { el.removeEventListener("touchmove", touchMove); el.removeEventListener("mousedown", mouseDownEvent); el.removeEventListener("mouseup", mouseUp); el.removeEventListener("mouseout", mouseOut); document.removeEventListener("mouseout", mouseOut); el.removeEventListener("mousemove", mouseMove); } }复制成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

v1.4.16