Declarative Shadow DOM 封装
FreeGuideOnline
最新
2026-07-13
这段文字在 Shadow DOM 内部,颜色为蓝色。
这段文字作为 Light DOM 内容,会被插入到 slot 中。
```shadowrootmode取值:"open":影子根开放,外部可通过element.shadowRoot访问。"closed":影子根关闭,外部无法获取(较少使用)。
<template>元素会被浏览器替换为实际的 Shadow Root,其内部 DOM 成为封装内容。
封装效果演示
1. 样式隔离
声明式 Shadow DOM 内部的样式仅影响影子树,不会泄漏到外部,外部样式也无法穿透。
<div>
<style>p { color: red; }</style>
<p>这是红色的外部段落。</p>
<user-card>
<template shadowrootmode="open">
<style>p { color: green; font-weight: bold; }</style>
<p>这张卡片内的文字为绿色加粗。</p>
</template>
</user-card>
</div>
上述代码中,外部的红色规则不会影响 <user-card> 内部的 <p>,内部的绿色规则也不会影响外部段落。
2. 插槽(Slot)投射
使用 <slot> 元素将 Light DOM 内容投射到影子树指定位置。
<my-panel>
<template shadowrootmode="open">
<h2>面板标题</h2>
<slot>默认内容,当没有传入任何内容时显示。</slot>
</template>
<p>这是插入的 Light DOM 段落。</p>
</my-panel>
在上例中,Light DOM 中的 <p> 会替换影子树内的 <slot> 元素。如果 <my-panel> 内部没有任何内容,则会显示“默认内容...”。
与自定义元素配合使用
Declarative Shadow DOM 常与自定义元素(Custom Elements)结合,实现完全封装的组件:
<my-greeting name="Alice">
<template shadowrootmode="open">
<style>
:host { display: block; border: 1px solid #ccc; padding: 1em; }
span { color: #0077cc; }
</style>
<p>你好,<span id="name"></span>!</p>
</template>
</my-greeting>
<script>
class MyGreeting extends HTMLElement {
constructor() {
super();
// 如果模板尚未被解析为 Shadow Root,则手动附加
// 但实际上,当 HTML 中存在 template[shadowrootmode] 时,浏览器已自动创建 Shadow Root
}
connectedCallback() {
const name = this.getAttribute('name');
const span = this.shadowRoot?.querySelector('#name');
if (span) span.textContent = name;
}
}
customElements.define('my-greeting', MyGreeting);
</script>
注意:在声明式 Shadow DOM 中,浏览器会自动将 <template shadowrootmode="open"> 转换为 Shadow Root 附加到父元素上。因此自定义元素的构造函数中无需再调用 attachShadow()。使用 this.shadowRoot 可直接获取影子根。
服务端渲染 (SSR) 支持
声明式 Shadow DOM 的最大优势之一是服务端可以预先输出完整的 DOM 结构:
<!-- 服务端输出的 HTML -->
<user-profile>
<template shadowrootmode="open">
<style>
:host { display: flex; align-items: center; }
.avatar { width: 40px; height: 40px; border-radius: 50%; background: #ddd; }
</style>
<div class="avatar"></div>
<span>张三</span>
</template>
</user-profile>
浏览器解析后立即呈现封装好的组件,无需等待任何 JavaScript。之后,可以通过 Hydration (水合作用)绑定交互逻辑,例如添加事件监听器,实现完全功能的组件。
使用场景与最佳实践
- 静态内容卡片:不依赖复杂 JS 交互的展示型组件(如文章预览、用户头像)。
- 渐进增强:先通过声明式 Shadow DOM 提供基础样式隔离,再通过 JS 添加交互。
- SEO 优化:服务端直接输出带 shadow 结构的内容,搜索引擎可以更好地抓取。
- 混合模式:在支持自定义元素的框架中,利用声明式 Shadow DOM 作为模板语法,减少运行时的模板编译开销。
浏览器兼容性
Declarative Shadow DOM 已在现代浏览器中得到支持:
- Chrome / Edge 90+
- Safari 16.4+
- Firefox 123+(实验性支持)
对于不支持的浏览器,可以使用 polyfill 或者保持命令式方式作为降级方案。
完整示例:创建一个提示框组件
下面是一个完整的声明式 Shadow DOM 组件示例,展示如何封装样式、使用插槽和属性。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>声明式 Shadow DOM 示例</title>
</head>
<body>
<info-banner type="info">
<template shadowrootmode="open">
<style>
:host { display: block; padding: 1em; margin: 1em 0; border-radius: 4px; }
:host([type="info"]) { background: #e7f3ff; color: #004085; }
:host([type="warn"]) { background: #fff3cd; color: #856404; }
:host([type="error"]) { background: #f8d7da; color: #721c24; }
.icon { margin-right: 0.5em; font-weight: bold; }
</style>
<span class="icon">•</span><slot></slot>
</template>
这是一条信息提示。
</info-banner>
<info-banner type="warn">
<template shadowrootmode="open">
<style>
:host { display: block; padding: 1em; margin: 1em 0; border-radius: 4px; }
:host([type="info"]) { background: #e7f3ff; color: #004085; }
:host([type="warn"]) { background: #fff3cd; color: #856404; }
:host([type="error"]) { background: #f8d7da; color: #721c24; }
.icon { margin-right: 0.5em; font-weight: bold; }
</style>
<span class="icon">⚠️</span><slot></slot>
</template>
请检查输入内容。
</info-banner>
</body>
</html>