Shadow DOM

所谓 Shadow DOM 指的是,浏览器将模板、样式表、属性、JavaScript 码等,封装成一个独立的 DOM 元素。外部的设置无法影响到其内部,而内部的设置也不会影响到外部,与浏览器处理原生网页元素(比如<video>元素)的方式很像。

Shadow DOM 最大的好处有两个,一是可以向用户隐藏细节,直接提供组件,二是可以封装内部样式表,不会影响到外部。

Custom Element 内部有一个 Shadow Root。它就是接入外部 DOM 的根元素。

  1. // attachShadow() creates a shadow root.
  2. let shadow = div.attachShadow({ mode: 'open' });
  3. let inner = document.createElement('b');
  4. inner.appendChild(document.createTextNode('Hiding in the shadows'));
  5. // shadow root supports the normal appendChild method.
  6. shadow.appendChild(inner);
  7. div.querySelector('b'); // empty

上面代码中,<div>包含<b>,但是 DOM 方法无法看到它,而且页面的样式也影响不到它。

mode: 'open'表示开发者工具里面,可以看到 Custom HTML 内部的 DOM,并与之互动。mode: closed将不允许 Custom Element 的使用者与内部代码互动。

Shadow root 内部通过指定innerHTML属性或使用<template>元素,指定 HTML 代码。

Shadow DOM 内部可以通过向根添加<style>(或<link>)来设置样式。

  1. let style = document.createElement('style');
  2. style.innerText = 'b { font-weight: bolder; color: red; }';
  3. shadowRoot.appendChild(style);
  4. let inner = document.createElement('b');
  5. inner.innerHTML = "I'm bolder in the shadows";
  6. shadowRoot.appendChild(inner);

上面代码添加的样式,只会影响 Shadow DOM 内的元素。

Custom Element 的 CSS 样式内部,:root表示这个根元素。比如,Custom Element 默认是行内元素,下面代码可以改成块级元素。

  1. :host {
  2. display: block;
  3. }
  4. :host([disabled]) {
  5. opacity: 0.5;
  6. }

注意,外部样式会覆盖掉:host的设置,比如下面的样式会覆盖:host

  1. my-element {
  2. display: inline-block;
  3. }

利用 CSS 的自定义属性,可以为 Custom Element 可以被覆盖的默认样式。下面是外部样式,my-element是 Custom Element。

  1. my-element {
  2. --background-color: #ff0000;
  3. }

然后,内部可以指定默认样式,用于用户没有指定颜色的情况。

  1. :host {
  2. --background-color: #ffffff;
  3. }
  4. #container {
  5. background-color: var(--background-color);
  6. }

下面的例子是为 Shadow DOM 加上独立的模板。

  1. <div id="nameTag">张三</div>
  2. <template id="nameTagTemplate">
  3. <style>
  4. .outer {
  5. border: 2px solid brown;
  6. }
  7. </style>
  8. <div class="outer">
  9. <div class="boilerplate">
  10. Hi! My name is
  11. </div>
  12. <div class="name">
  13. Bob
  14. </div>
  15. </div>
  16. </template>

上面代码是一个div元素和模板。接下来,就是要把模板应用到div元素上。