事件隔离
Shadow DOM,它不仅仅可以做到 DOM 元素的 CSS 样式隔离,还可以做到事件的隔离处理,例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<template id="custom-element-template">
<h1>Shadow DOM 中的标题</h1>
<!-- button 按钮会渲染在 Shadow DOM 中 -->
<button onclick="handleShadowButtonClick(event)">
Shadow DOM 中的按钮
</button>
</template>
<!-- 普通 DOM 测试-->
<div id="outer" onclick="handleOuterClick(event)">
<h1>普通 DOM 中的标题</h1>
<!-- button 按钮在普通 DOM 下 --->
<button onclick="handleButtonClick(event)">普通 DOM 中的按钮</button>
</div>
<!-- Shadow DOM 测试-->
<div id="shadow-outer" onclick="handleShadowOuterClick(event)">
<!-- Shadow Host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上 -->
<!-- template 中的 button 按钮元素的 DOM 副本会被拷贝到该 Shadow Host 下 --->
<custom-element onclick="handleShadowHostClick(event)"></custom-element>
</div>
<script>
// 普通 DOM 事件
function handleButtonClick(e) {
console.log("handleButtonClick: ", e.target);
}
// 普通 DOM 事件
function handleOuterClick(e) {
console.log("handleOuterClick: ", e.target);
}
// Shadow DOM 事件
function handleShadowButtonClick(e) {
console.log("handleShadowButtonClick: ", e.target);
}
// Shadow DOM 事件
function handleShadowHostClick(e) {
console.log("handleShadowHostClick: ", e.target);
}
// 普通 DOM 事件
function handleShadowOuterClick(e) {
console.log("handleShadowOuterClick: ", e.target);
}
// 全局 document 委托事件
document.onclick = function (e) {
console.log("document.onclick: ", e.target);
};
class CustomElement extends HTMLElement {
constructor() {
super();
// Shadow Root: Shadow Tree 的根节点
const shadowRoot = this.attachShadow({ mode: "open" });
const $template = document.getElementById("custom-element-template");
// cloneNode:
// 克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件 (比如 onclick="alert(1)"),
// 但不会拷贝那些使用 addEventListener() 方法或者 node.onclick = fn 这种用 JavaScript 动态绑定的事件。
shadowRoot.appendChild($template.content.cloneNode(true));
}
}
customElements.define("custom-element", CustomElement);
</script>
</body>
</html>.这就会导致一个问题:尽管 DOM 事件不会被阻断,但是 Shadow Root 之上的父节点已经无法获取精准的事件对象。
熟悉 React 开发的同学,会发现 React 17 以下会使用 Document 进行事件委托处理,此时会因为拿不到 Shadow DOM 中的事件对象,而导致事件失效。为了解决类似的问题,React 17 不再使用 Document 进行事件委托,而是使用 React 挂载的 Root 节点进行事件委托,此时如果在 Shadow DOM 中使用 React 框架,那么事件可以被正确处理