使用影子 DOM - Web API | MDN 自定义元素的一个重要方面是封装,因为自定义元素从定义上来说是一种可重用功能:它可以被放置在任何网页中,并且期望它能够正常工作。因此,很重要的一点是,运行在页面中的代码不应该能够通过修改自定义元素的内部实现而意外地破坏它。 影子 DOM(Shadow DOM)允许你将一个 DOM 树附加到一个元素上,并且使该树的内部对于在页面中运行的 JavaScript 和 CSS 是隐藏的。 影子 DOM 允许将隐藏的 DOM 树附加到常规 DOM 树中的元素上——这个影子 DOM 始于一个影子根,在其之下你可以用与普通 DOM 相同的方式附加任何元素。 创建一个影子 DOM
I'm not in the shadow DOM 我们将使用 "host" 元素作为影子宿主。我们调用宿主上的 attachShadow() 来创建影子 DOM,然后可以像我们将节点添加到主 DOM 一样将节点添加到影子 DOM 中。在这个示例中,我们添加了单个 元素: ment.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "I'm in the shadow DOM";shadow.appendChild(span); Element.shadowRoot 和“mode”选项 在上面的例子中,我们将一个参数 { mode: "open" } 传入 attachShadow()。当 mode 设置为 "open" 时,页面中的 JavaScript 可以通过影子宿主的 shadowRoot 属性访问影子 DOM 的内部。 在这个例子中,就像之前一样,HTML 包含影子宿主、主 DOM 树中的一个 元素以及两个按钮: I'm not in the shadow DOM这次,“大写”按钮使用 shadowRoot 来找到 DOM 中的 元素: const host = document.querySelector("#host");const shadow = host.attachShadow({ mode: "open" });const span = document.createElement("span");span.textContent = "I'm in the shadow DOM";shadow.appendChild(span);const upper = document.querySelector("button#upper");upper.addEventListener("click", () => { const spans = Array.from(host.shadowRoot.querySelectorAll("span")); for (const span of spans) { span.textContent = span.textContent.toUpperCase(); }});const reload = document.querySelector("#reload");reload.addEventListener("click", () => document.location.reload()); 这次,页面中运行的 JavaScript 可以访问影子 DOM 内部 使用 {mode: "open"} 参数为页面提供一种破坏影子 DOM 封装的方法。如果你不希望给页面这个能力,传递 {mode: "closed"} 作为替代,此时 shadowRoot 返回 null。 然而,你不应将这视为一个强大的安全机制,因为它可以被绕过,比如通过在页面中运行的浏览器扩展。这更多地是一个指示页面不应访问影子 DOM 树内部的一种提示。 在影子 DOM 内应用样式 在这个部分,我们将看到两种不同的方法来在影子 DOM 树中应用样式: 编程式,通过构建一个 CSSStyleSheet 对象并将其附加到影子根。 声明式,通过在一个



