为什么需要 Shadow DOM?

Permalink to "为什么需要 Shadow DOM?"

你可能注意到,在上一步中添加的样式会选中页面上所有的 <rating-element> 以及所有的 <button>。这可能会导致样式泄漏出元素,选中你本不打算设置样式的其他节点。而你自定义元素外部的样式也可能无意中影响元素内部节点的样式。

例如,尝试在主文档的 head 中添加一个 style 标签:

index.html

Permalink to "index.html"
<!DOCTYPE html>
<html>
<head>
<script src="./rating-element.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>

你应该会看到评分的 span 周围有一个红色边框。这是一个简单的例子,但对于更复杂的应用程序,缺少 DOM 封装可能会导致更大的问题。这就是 Shadow DOM 发挥作用的地方。

附加 Shadow Root

Permalink to "附加 Shadow Root"

connectedCallback 内部,为元素附加一个 shadow root,并将 DOM 渲染在该 root 内部:

rating-element.

Permalink to "rating-element."
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});

// 注意新的 CSS 选择器
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}

现在红色边框应该消失了——主文档中的样式再也无法选中 shadow root 内部的节点。

你是怎么做到的?在 connectedCallback 中你调用了 this.attachShadow,它为元素附加了一个 shadow root。open 模式意味着 shadow 内容是可检查的,并且可以通过 this.shadowRoot 访问 shadow root。同时在 Chrome 检查器中查看这个 Web Component:

Chrome 检查器中的 DOM 树。有一个 <rating-element>,其子节点是 #shadow-root (open),shadow root 内部是之前的 DOM。

你现在应该能在组件内部看到一个 shadow root。你可以展开 shadow root 来查看 Shadow DOM 的内容。如果你在 Chrome 开发者工具中选中 rating 元素并调用 $0.children,你会发现它返回空。Shadow DOM 是它自己的 DOM 树,与组件的直接子节点是分离的。

Light DOM

Permalink to "Light DOM"

一个实验:添加一个节点作为 <rating-element> 的直接子节点:

index.html

Permalink to "index.html"
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>

页面刷新后,你会看到新的 <div> 没有显示出来。这是因为 Shadow DOM 具有控制子节点(有时称为 Light DOM)如何被渲染或投射到 Shadow DOM 中的功能。

本教程不涉及 Light DOM 投射。

你可以在这篇文章Lit 文档中了解更多关于投射的内容。