开发中经常会遇这样的需求:点击 A 元素的时候,需要触发 B 元素的事件,比如:点击一个 div 元素,然后触发 input:file 的 click 事件,用来选择文件上传。
click 方法
以上需求可通过元素的 click 方法触发:
html
<style>
.test2 {
padding: 4px;
margin-top: 12px;
}
</style>
<input type="file" class="test1"><br>
<button class="test2">前端路引--事件测试</button>
<script>
(() => {
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
test2.addEventListener('click', () => {
// 点击 test2 后,触发 test1 的 click 事件
test1.click()
})
})()
</script>
效果:
点击事件有 click
方法可以使用,如果需求是点击 A 元素触发 B 元素的 mousedown
事件,那能用 mousedown
方法吗?
html
<style>
button {
padding: 4px;
margin-top: 12px;
}
</style>
<button class="test1">前端路引--事件测试 test1</button><br>
<button class="test2">前端路引--事件测试 test2</button>
<script>
(() => {
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
test1.addEventListener('mousedown', () => {
console.log('test1 mousedown')
})
test2.addEventListener('click', () => {
// 点击 test2 后,触发 test1 的 click 事件
test1.click()
test1.mousedown();
})
})()
</script>
效果:
想法在实现上遇到了问题,html 元素上不存在 mousedown
方法,click
方法也只能触发 click 事件,无法触发绑定的 mousedown 事件,那么有办法可以做到吗?答案肯定是有的~~
dispatchEvent 方法
dispatchEvent 方法可以触发任意事件,其参数是一个实例化的 Event 对象。以上例子中使用 dispatchEvent 方法,实现效果如下:
html
<style>
button {
padding: 4px;
margin-top: 12px;
}
</style>
<button class="test1">前端路引--事件测试 test1</button><br>
<button class="test2">前端路引--事件测试 test2</button>
<script>
(() => {
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
test1.addEventListener('mousedown', () => {
console.log('test1 mousedown')
})
test2.addEventListener('click', () => {
// 点击 test2 后,触发 test1 的 click 事件
const event = new Event('mousedown')
test1.dispatchEvent(event)
})
})()
</script>
效果:
Event 对象
语法:
js
new Event(type, options)
该对象有两个参数:
第一个事件类型 type
为必传参数,不传则报错 Uncaught TypeError: Failed to construct 'Event': 1 argument required, but only 0 present.
第二个 options
参数为可选参数,拥有三个属性:
bubbles
: 可选,Boolean 类型,默认值为 false,表示该事件是否冒泡。
cancelable
: 可选,Boolean 类型,默认值为 false,表示该事件能否被取消。
composed
: 可选,Boolean 类型,默认值为 false,指示事件是否会在影子 DOM 根节点之外触发侦听器。
bubbles 冒泡:
关于冒泡很好理解,就是子元素是否能冒泡到父元素,如下例子 test1 触发的事件将会冒泡到容器 test-container 上。
html
<div class="test-container">
<button class="test0">前端路引--事件测试 test0</button>
</div>
<button class="test1">前端路引--事件测试 test1</button>
<button class="test2">前端路引--事件测试 test2</button>
<script>
(() => {
const test0 = document.querySelector('.test0')
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
document.querySelector('.test-container').addEventListener('mousedown', () => {
console.log('test-container mousedown')
})
test1.addEventListener('click', () => {
// 默认不允许冒泡
const event = new Event('mousedown')
test0.dispatchEvent(event)
})
test2.addEventListener('click', () => {
// 配置 bubbles 允许冒泡
const event = new Event('mousedown', { bubbles: true })
test0.dispatchEvent(event)
})
})()
</script>
效果:
cancelable 事件能否取消:
true
:表示事件是可取消的,调用 event.preventDefault() 会阻止浏览器的默认行为。
false
:表示事件不可取消,调用 event.preventDefault() 无效。
实测就算传入的是传入为 false
,也能调用 event.preventDefault() 并不会报错,对 Event 对象无效,但是对 MouseEvent
对象有用(参考后文)。
设置此参数可在事件的 event 参数上获取传入的值:
js
<input type="file" name="" id="" class="file"><br>
<button class="test1">前端路引--事件测试 cancelable: false</button><br>
<button class="test2">前端路引--事件测试 cancelable: true</button>
<script>
(() => {
const file = document.querySelector('.file')
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
file.addEventListener('click', () => {
console.log('cancelable', event.cancelable);
})
test1.addEventListener('click', () => {
const event = new Event('click', { cancelable: false })
file.dispatchEvent(event)
})
test2.addEventListener('click', () => {
const event = new Event('click', { cancelable: true })
file.dispatchEvent(event)
})
})()
</script>
效果:
dispatchEvent 触发的 Event 对象,并
不会响应
元素本身的默认事件,比如 a 标签的跳转,input:file 的文件选择等。
composed 是否允许穿透 shadow DOM 节点:
如果不使用影子节点,这属性基本没啥用处,影子节点在常规开发中很少使用~~
一个简单示例:
html
<div class="wrapper">
<my-component id="host"></my-component>
</div>
<script>
(() => {
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<button id="inner-button1">影子节点内部按钮1--前端路引</button>
<button id="inner-button2">影子节点内部按钮2--前端路引</button>
`;
}
}
customElements.define('my-component', MyComponent);
const host = document.querySelector('#host');
const innerBtn1 = host.shadowRoot.querySelector('#inner-button1')
const innerBtn2 = host.shadowRoot.querySelector('#inner-button2')
innerBtn1.addEventListener('click', () => {
const event = new Event('custom-event', {
bubbles: true, // 允许在 Shadow DOM 内部冒泡
composed: false // 禁止穿透到外部 DOM
});
innerBtn1.dispatchEvent(event);
});
innerBtn2.addEventListener('click', () => {
const event = new Event('custom-event', {
bubbles: true, // 允许在 Shadow DOM 内部冒泡
composed: true // 允许穿透到外部 DOM
});
innerBtn2.dispatchEvent(event);
});
// 尝试在外部 DOM 监听事件
host.addEventListener('custom-event', () => {
console.log('外部 DOM 监听到事件'); // 不会触发
});
})()
</script>
效果:
CustomEvent 对象
Event
对象没办法传入自定义数据,某些特定需求需要传入自定义参数时,可以祭出 CustomEvent
对象。
CustomEvent 继承 Event,所以 Event 支持的配置都支持,只是多了一个自定义数据字段。
示例:
html
<button class="test1">前端路引--事件测试 test1</button><br>
<button class="test2">前端路引--事件测试 test2</button>
<script>
(() => {
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
test1.addEventListener('dev', (event) => {
console.log('自定义数据:', event.detail);
})
test2.addEventListener('click', () => {
// 点击 test2 后,触发 test1 的 click 事件
const event = new CustomEvent('dev', {
detail: {
name: '前端路引',
age: 1
}
})
test1.dispatchEvent(event)
})
})()
</script>
以上代码使用了 CustomEvent
触发了自定义的 dev
事件,并传入了自定义数据。
效果:
MouseEvent
前面说了 dispatchEvent 触发的 Event 对象不会响应元素本身的默认事件,但可以通过 MouseEvent 对象来触发一些元素本身的默认事件。
如下例子:
html
<input type="file" name="" id="" class="file"><br>
<button class="test1">前端路引--事件测试 cancelable: false</button><br>
<button class="test2">前端路引--事件测试 cancelable: true</button>
<script>
(() => {
const file = document.querySelector('.file')
const test1 = document.querySelector('.test1')
const test2 = document.querySelector('.test2')
file.addEventListener('click', () => {
event.preventDefault()
console.log('cancelable', event.cancelable);
})
test1.addEventListener('click', () => {
const event = new MouseEvent('click', { cancelable: false })
const res = file.dispatchEvent(event)
console.log(`${!res ? '调用了' : '没调用'} preventDefault`);
})
test2.addEventListener('click', () => {
const event = new MouseEvent('click', { cancelable: true })
const res = file.dispatchEvent(event)
console.log(`${!res ? '调用了' : '没调用'} preventDefault`);
})
})()
</script>
效果:
dispatchEvent 返回值当 event 可被取消(cancelable 值为 true),且 event 中至少有一个事件处理程序调用了 Event.preventDefault() 方法时,返回 false。否则,返回 true。
写在最后
按照 MDN 的说法,由程序触发的事件,还有一个专用名词 合成事件
,表示不是浏览器本身触发的事件。
除了本文例子中的几个 Event 对象外,还有一些其他对象,在使用时可参考 MDN 文档。
用户交互:
鼠标、键盘、触摸交互 MouseEvent
, KeyboardEvent
, TouchEvent
表单与输入:
输入框、表单提交 InputEvent
, SubmitEvent
媒体控制:
音视频播放、设备流 MediaStreamTrackEvent
拖放与剪贴板:
拖拽操作、复制粘贴 DragEvent
, ClipboardEvent
存储与通信:
本地存储、跨文档通信 StorageEvent
, MessageEvent
错误与调试:
脚本错误捕获 ErrorEvent
设备与传感器:
方向、加速度检测 DeviceOrientationEvent
动画与过渡:
CSS 动画/过渡生命周期 AnimationEvent
, TransitionEvent