基本构件介绍

Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。

  • Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates(HTML模板): <template><slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

HTML templates

<slot> 元素只能在 <template> 元素中使用,在 <template> 元素外表现和 <div> 元素一致。

Shadow DOM

shadowRoot mod

打开的shadow root允许你使用host元素的 shadowRoot 属性从 root 外部访问 shadow root 的元素,如下例所示:

  1. <div>
  2. <p>Light DOM</p>
  3. </div>
  4. <div id="host"></div>
  5. <script>
  6. const elem = document.querySelector('#host');
  7. // attach an open shadow root to #host
  8. const shadowRoot = elem.attachShadow({mode: 'open'});
  9. shadowRoot.innerHTML = `<p>Shadow DOM</p>`;
  10. // Nodes of an open shadow DOM are accessible
  11. // from outside the shadow root
  12. elem.shadowRoot.querySelector('p').innerText = 'Changed from outside the shadow root';
  13. elem.shadowRoot.querySelector('p').style.color = 'red';
  14. </script>

但是如果 mode 属性的值为“closed”,则尝试从 root 外部用 JavaScript 访问 shadow root 的元素时会抛出一个 TypeError

并非所有 HTML 元素都可以托管 Shadow DOM DOM

下表列出了支持的元素:

  • article
  • article
  • blockquote
  • div
  • h1-h6
  • body
  • footer
  • header
  • main
  • nav
  • p
  • section
  • span

尝试将 Shadow DOM 树附加到其他元素将会导致 DOMException 错误。

浏览器自动将shadow DOM附加到某些元素

Shadow DOM已存在很长一段时间了,浏览器一直用它来隐藏元素的内部结构,比如<input><textarea><video>

在自定义元素上托管shadow DOM

Custom Elements API 创建的自定义元素可以像其他元素一样托管shadow DOM。请看以下示例:

  1. <my-element></my-element>
  2. <script>
  3. class MyElement extends HTMLElement {
  4. constructor() {
  5. // must be called before the this keyword
  6. super();
  7. // attach a shadow root to <my-element>
  8. const shadowRoot = this.attachShadow({mode: 'open'});
  9. shadowRoot.innerHTML = `
  10. <style>p {color: red}</style>
  11. <p>Hello</p>`;
  12. }
  13. }
  14. // register a custom element on the page
  15. customElements.define('my-element', MyElement);
  16. </script>

此代码了创建一个托管shadow DOM的自定义元素。它调用了 customElements.define() 方法,元素名称作为第一个参数,类对象作为第二个参数。该类扩展了 HTMLElement 并定义了元素的行为。

在构造函数中,super() 用于建立原型链,并且把 Shadow root 附加到自定义元素。当你在页面上使用 <my-element> 时,它会创建自己的 Shadow DOM:

image.png

请记住,有效的自定义元素不能是单个单词,并且名称中必须包含连字符( - )。例如,myelement 不能用作自定义元素的名称,并会抛出 DOMException 错误。

样式化 host 元素

shadow host 表示shadow DOM子树的根节点

CSS 伪类与自定义元素特别相关的伪类:

  • :defined: 匹配任何已定义的元素,包括内置元素和使用 CustomElementRegistry.define() 定义的自定义元素。
  • :host: 选择 shadow DOM 的 shadow host ,内容是它内部使用的 CSS( containing the CSS it is used inside )。
  • :host(): 选择 shadow DOM 的 shadow host ,内容是它内部使用的 CSS (这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器的 shadow host 元素。
  • :host-context(): 选择 shadow DOM 的 shadow host ,内容是它内部使用的 CSS (这样您可以从 shadow DOM 内部选择自定义元素)— 但只匹配给定方法的选择器匹配元素的子 shadow host 元素。

生命周期回调

定义在自定义元素的类定义中的特殊回调函数,影响其行为:

  • connectedCallback:当自定义元素第一次被连接到文档 DOM 时被调用。
  • disconnectedCallback:当自定义元素与文档 DOM 断开连接时被调用。
  • adoptedCallback:当自定义元素被移动到新文档时被调用。
  • attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调

API

CustomElementRegistry

使用 CustomElementRegistry.define() 方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。CustomElements 接口返回一个 CustomElementRegistry 对象的引用,可用于注册新的 custom elements,或者获取之前定义过的自定义元素的信息。

代码示例

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Learn Web Components</title>
  7. </head>
  8. <body>
  9. <hello-word text="hello" id="hello-word">
  10. <span slot="context">插槽内容</span>
  11. </hello-word>
  12. <!-- 组件的样式只对 template 内部的标签有效 -->
  13. <template id="hw-template">
  14. <style>
  15. p {
  16. padding: 10px;
  17. background-color: #f40;
  18. color: #fff;
  19. }
  20. </style>
  21. <p>Hello Web Components</p>
  22. <p>
  23. <slot name="context"></slot>
  24. </p>
  25. </template>
  26. <script>
  27. // 所有的组件类必须继承 HTMLElement
  28. class HelloWord extends HTMLElement {
  29. constructor() {
  30. super();
  31. // 得到模板内容
  32. const templateContent = document.querySelector('#hw-template').content;
  33. // 创建一个影子 DOM 节点作为组件的根元素
  34. const shadowRoot = this.attachShadow({ mode: 'open' })
  35. // 将组件节点添加到根元素
  36. shadowRoot.appendChild(templateContent.cloneNode(true))
  37. // 获得组件 text 属性的值
  38. const text = this.getAttribute('text');
  39. shadowRoot.querySelector('p').innerText = text;
  40. }
  41. // 第一次连接文档 DOM 生命周期函数
  42. connectedCallback() {
  43. console.log('第一次被连接到文档 DOM 时被调用');
  44. }
  45. }
  46. customElements.define('hello-word', HelloWord);
  47. </script>
  48. </body>
  49. </html>

从浏览器打开你可以看到

image.png

参考

【1】Web Components | MDN
【2】Web Component可以取代你的前端框架吗
【3】深入理解Shadow DOM v1
【4】Web Components零基础实战教学(B站视频)
【5】google polymer 框架