认识Shadow DOM

408 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

两类DOM tree

  • 一个DOM元素通常包含两类DOM子树

    • Light tree:一般的DOM树,由HTML子元素组成
    • Shadow tree:一个隐藏的DOM子树,不在HTML中反应

如果一个元素同时拥有以上两种DOM子树,那么浏览器只渲染shadow tree。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script>
    customElements.define('shadow-test', class extends HTMLElement {
      connectedCallback() {
        const shadow = this.attachShadow({mode: 'open'});
        shadow.innerHTML = "这是Shadow DOM元素"
        const div = document.createElement("div")
        div.innerHTML = "这是Light DOM元素"
        this.append(div)
      }
    });
    </script>
<body>
    <shadow-test></shadow-test> 
</body>
</html>

同时存在shadow tree和light tree显示结果如下:

shadow与light.png

通常我们使用Shadow dom是在自定义元素中使用,目的是为了隐藏组件内部结构和有效样式

Shadow DOM概念

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

shadow-dom.png

  • 一些术语:

    • Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
    • Shadow tree:Shadow DOM内部的DOM树。
    • Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
    • Shadow root: Shadow tree的根节点。
  • 就像操作常规DOM一样,我们可以为shadow dom添加子节点,设置属性,以及添加样式,shadow dom内部的元素始终不会影响到外界元素(除了 :focus-within)。

内建Shadow DOM

  • 一些复制的浏览器控件如: <input><video>等,浏览器在内部DOM/CSS来绘制他们,而这个结构一般是隐藏的,我们不能使用一般的 JavaScript 调用或者选择器来获取内建 shadow DOM 元素。它们不是常规的子元素,而是一个强大的封装手段。
  • 例如:input元素在Chrome浏览器中的表现为

input-shadow-dom.png

基本用法

  • 我们可以调用elem.attachShadow({mode: "opne"/"closed"})来创建一个shadow root

    • 每个元素中只能有一个shadow root

    • elem必须是自定义元素,或者是以下元素中其中一个:articleasideblockquotebodydivfooterh1h1h2h3h4h5h6headermainnavpsectionspan。其他元素则不能容纳shadow tree

    • mode选择的值为open或者closed:

      • open: shadow root 可以通过 elem.shadowRoot 访问。例如可以使用elem.shadowRoot访问
      let ShadowDom = customElem.shadowRoot;
      
      • closed: elem.shadowRoot 永远是 null

        • 我们只能通过 attachShadow 返回的指针来访问 shadow DOM(并且可能隐藏在一个 class 中)。
        • 例如在原生中video,input是封闭的shadow dom 没有任何方法可以访问他们
  • shadow dom会和主文档分开

    • Shadow DOM 元素对于 light DOM 中的 querySelector 不可见。实际上,Shadow DOM 中的元素可能与 light DOM 中某些元素的 id 冲突。这些元素必须在 shadow tree 中独一无二。

    • Shadow DOM 有自己的样式。外部样式规则在 shadow DOM 中不产生作用。

      • 可以使用elem.style设置样式
      • 也可以创建<style>元素为shadow tree添加全局样式
      • 还可以通过创建<link>标签引入外部样式
    // 将外部引用的样式添加到 Shadow DOM 上
    const linkElem = document.createElement('link');
    linkElem.setAttribute('rel', 'stylesheet');
    linkElem.setAttribute('href', 'style.css');
    ​
    // 将所创建的元素添加到 Shadow DOM 上
    ​
    shadow.appendChild(linkElem);
    
    • 获取 shadow tree 内部的元素,我们可以从树的内部查询
    • 拥有自己的id空间