aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/web_components/using_shadow_dom/index.html
blob: 1267f495087403bd1b2b31a782ebb39458ff38aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
---
title: 使用 shadow DOM
slug: Web/Web_Components/Using_shadow_DOM
tags:
  - API
  - DOM
  - Web Components
  - 指南
translation_of: Web/Web_Components/Using_shadow_DOM
---
<div>{{DefaultAPISidebar("Web Components")}}</div>

<p class="summary">Web components 的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。本篇文章将会介绍 Shadow DOM 的基础使用。</p>

<div class="note">
<p><strong>备注:</strong> Firefox(从版本 63 开始),Chrome,Opera 和 Safari 默认支持 Shadow DOM。基于 Chromium 的新 Edge 也支持 Shadow DOM;而旧 Edge 未能撑到支持此特性。</p>
</div>

<h2 id="概况">概况</h2>

<p>本文章假设你对 <a href="/en-US/docs/Web/API/Document_Object_Model/Introduction">DOM(文档对象模型)</a>有一定的了解,它是由不同的元素节点、文本节点连接而成的一个树状结构,应用于标记文档中(例如  Web 文档中常见的 HTML 文档)。请看如下示例,一段 HTML 代码:</p>

<pre class="brush: html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;title&gt;Simple DOM example&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;section&gt;
        &lt;img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth."&gt;
        &lt;p&gt;Here we will add a link to the &lt;a href="https://www.mozilla.org/"&gt;Mozilla homepage&lt;/a&gt;&lt;/p&gt;
      &lt;/section&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>

<p>这个片段会生成如下的 DOM 结构:</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/14559/dom-screenshot.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p>

<p><em>Shadow</em> DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/15788/shadow-dom.png" style="height: 543px; width: 1138px;"></p>

<p>这里,有一些 Shadow DOM 特有的术语需要我们了解:</p>

<ul>
 <li>Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。</li>
 <li>Shadow tree:Shadow DOM内部的DOM树。</li>
 <li>Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。</li>
 <li>Shadow root: Shadow tree的根节点。</li>
</ul>

<p>你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样——例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 <code>element.style</code> 属性),或者为整个 Shadow DOM 添加样式(例如在 {{htmlelement("style")}} 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 {{CSSxRef(":focus-within")}}),这为封装提供了便利。</p>

<p>注意,不管从哪个方面来看,Shadow DOM 都不是一个新事物——在过去的很长一段时间里,浏览器用它来封装一些元素的内部结构。以一个有着默认播放控制按钮的 {{htmlelement("video")}} 元素为例。你所能看到的只是一个 <code>&lt;video&gt;</code> 标签,实际上,在它的 Shadow DOM 中,包含来一系列的按钮和其他控制器。Shadow DOM 标准允许你为你自己的元素(custom element)维护一组 Shadow DOM。</p>

<h2 id="基本用法">基本用法</h2>

<p>可以使用 {{domxref("Element.attachShadow()")}} 方法来将一个 shadow root 附加到任何一个元素上。它接受一个配置对象作为参数,该对象有一个 <code>mode</code> 属性,值可以是 <code>open</code> 或者 <code>closed</code></p>

<pre class="brush: js">let shadow = elementRef.attachShadow({mode: 'open'});
let shadow = elementRef.attachShadow({mode: 'closed'});</pre>

<p><code>open</code> 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM,例如使用 {{domxref("Element.shadowRoot")}} 属性:</p>

<pre class="brush: js">let myShadowDom = myCustomElem.shadowRoot;</pre>

<p>如果你将一个 Shadow root 附加到一个 Custom element 上,并且将 <code>mode</code> 设置为 <code>closed</code>,那么就不可以从外部获取 Shadow DOM 了——<code>myCustomElem.shadowRoot</code> 将会返回 <code>null</code>。浏览器中的某些内置元素就是如此,例如<code>&lt;video&gt;</code>,包含了不可访问的 Shadow DOM。</p>

<div class="note">
<p><strong>备注:</strong> 正如<a href="https://blog.revillweb.com/open-vs-closed-shadow-dom-9f3d7427d1af">这篇文章所阐述的</a>,处理 closed Shadow DOM 实际上很简单,往往也很值得这么做。</p>
</div>

<p>如果你想将一个 Shadow DOM 附加到 custom element 上,可以在 custom element 的构造函数中添加如下实现(目前,这是 shadow DOM 最实用的用法):</p>

<pre class="brush: js">let shadow = this.attachShadow({mode: 'open'});</pre>

<p>将 Shadow DOM 附加到一个元素之后,就可以使用 DOM APIs对它进行操作,就和处理常规 DOM 一样。</p>

<pre class="brush: js">var para = document.createElement('p');
shadow.appendChild(para);
etc.</pre>

<h2 id="编写简单示例">编写简单示例</h2>

<p>现在,让我们着手实现一个示例——<code><a href="https://github.com/mdn/web-components-examples/tree/master/popup-info-box-web-component">&lt;popup-info-box&gt;</a></code>(也可以查看<a href="https://mdn.github.io/web-components-examples/popup-info-box-web-component/">在线示例</a>),来说明 Shadow DOM 在 custom element 中的实际运用。它包含一个图片 icon 和一段文字,并将 icon 嵌入页面之中。每当 icon 获取到焦点时,文字会在一个弹框中显示,以提供更加详细的信息。首先,在 JavaScript 文件中,我们需要定义一个叫做 <code>PopUpInfo</code> 的类,它继承自 <code>HTMLElement</code></p>

<pre class="brush: js">class PopUpInfo extends HTMLElement {
  constructor() {
    // 必须首先调用 super方法
    super();

    // 元素的具体功能写在下面

    ...
  }
}</pre>

<p>在上面的类中,我们将会在它的构造函数中定义元素所有的功能。当类实例化后,所有的实例元素都会有相同功能。</p>

<h3 id="创建_shadow_root">创建 shadow root</h3>

<p>在构造函数中,我们首先将 Shadow root 附加到 custom element 上:</p>

<pre class="brush: js">// 创建 shadow root
var shadow = this.attachShadow({mode: 'open'});</pre>

<h3 class="brush: js" id="创建_shadow_DOM_结构">创建 shadow DOM 结构</h3>

<p class="brush: js">接下来,我们会使用相关 DOM 操作来创建元素的 Shadow DOM 结构:</p>

<pre class="brush: js">// 创建 span
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');
var icon = document.createElement('span');
icon.setAttribute('class','icon');
icon.setAttribute('tabindex', 0);
var info = document.createElement('span');
info.setAttribute('class','info');

// 获取属性的内容并将内容添加到 info 元素内
var text = this.getAttribute('text');
info.textContent = text;

// 插入 icon
var imgUrl;
if(this.hasAttribute('img')) {
  imgUrl = this.getAttribute('img');
} else {
  imgUrl = 'img/default.png';
}
var img = document.createElement('img');
img.src = imgUrl;
icon.appendChild(img);
</pre>

<h3 class="brush: js" id="为_shadow_DOM_添加样式">为 shadow DOM 添加样式</h3>

<p class="brush: js">之后,我们将要创建 {{htmlelement("style")}} 元素,并加入一些 CSS 样式:</p>

<pre class="brush: js line-numbers language-js">// 为 shadow DOM 添加一些 CSS 样式
var style = document.createElement('style');

style.textContent = `
.wrapper {
  position: relative;
}

.info {
  font-size: 0.8rem;
  width: 200px;
  display: inline-block;
  border: 1px solid black;
  padding: 10px;
  background: white;
  border-radius: 10px;
  opacity: 0;
  transition: 0.6s all;
  position: absolute;
  bottom: 20px;
  left: 10px;
  z-index: 3;
}

img {
  width: 1.2rem;
}

.icon:hover + .info, .icon:focus + .info {
  opacity: 1;
}`;</pre>

<h3 id="将_Shadow_DOM_添加到_Shadow_root_上">将 Shadow DOM 添加到 Shadow root 上</h3>

<p>最后,将所有创建的元素添加到 Shadow root 上:</p>

<pre class="brush: js">// 将所创建的元素添加到 Shadow DOM 上
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);</pre>

<h3 id="使用我们的_custom_element">使用我们的 custom element</h3>

<p>完成类的定义之后,使用元素也是一样简单,只需将 custom element 放在页面上,正如 <a href="/en-US/docs/Web/Web_Components/Using_custom_elements">Using custom elements</a> 中讲解的那样:</p>

<pre class="brush: js">// 定义新的元素
customElements.define('popup-info', PopUpInfo);</pre>

<pre class="brush: html">&lt;popup-info img="img/alt.png" text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."&gt;</pre>

<div>
<div id="tap-translate"></div>
</div>

<h3 id="使用外部引入的样式">使用外部引入的样式</h3>

<p>在上面的示例中,我们使用行内{{htmlelement("style")}} 元素为Shadow DOM添加样式,但是完全可以通过{{htmlelement("link")}} 标签引用外部样式表来替代行内样式。</p>

<p>例如,我们可以看下 <a href="https://mdn.github.io/web-components-examples/popup-info-box-external-stylesheet/">popup-info-box-external-stylesheet</a> 例子 (查看源代码 <a href="https://github.com/mdn/web-components-examples/blob/master/popup-info-box-external-stylesheet/main.js">source code</a>):</p>

<pre class="brush: js">// 将外部引用的样式添加到 Shadow DOM 上
const linkElem = document.createElement('link');
linkElem.setAttribute('rel', 'stylesheet');
linkElem.setAttribute('href', 'style.css');

// 将所创建的元素添加到 Shadow DOM 上

shadow.appendChild(linkElem);</pre>

<p>请注意, 因为{{htmlelement("link")}} 元素不会打断 shadow root 的绘制, 因此在加载样式表时可能会出现未添加样式内容(FOUC),导致闪烁。</p>

<p>许多现代浏览器都对从公共节点克隆的或具有相同文本的{{htmlelement("style")}} 标签实现了优化,以允许它们共享单个支持样式表,通过这种优化,外部和内部样式的性能表现比较接近。</p>

<h2 id="参见">参见</h2>

<ul>
 <li><a href="/zh-CN/docs/Web/Web_Components/Using_custom_elements">使用 custom elements</a></li>
 <li><a href="/zh-CN/docs/Web/Web_Components/Using_templates_and_slots">使用 templates 与 slots</a></li>
</ul>