Web Component

Web Component 是一套由 W3C 标准化的原生 Web 技术,旨在通过封装实现可复用、高内聚的定制化 HTML 元素。Web Component 的核心目标是提供一种标准化的方式来创建和使用自定义的 HTML 标签,同时确保这些标签的行为、样式和结构能够被完全封装,不会受到外部代码的影响。

1、Web Component 的简介

Web Component 其核心技术包括以下三部分:

  1. Custom Elements(自定义元素
    允许开发者创建新的 HTML 标签,通过继承 HTMLElement 类定义组件逻辑。例如,可注册 <my-button> 标签并绑定行为‌。
  2. Shadow DOM(影子 DOM
    提供封装机制,将组件的 DOM 和样式与主文档隔离,避免全局污染。通过 attachShadow({ mode: 'open' }) 创建独立作用域‌。
  3. HTML Templates(HTML 模板
    使用 <template><slot> 标签定义可复用的 HTML 结构,支持动态内容插入。模板内容不会直接渲染,需通过 JavaScript 激活‌。

1.1 优点

  1. 高度封装性
    Shadow DOM 实现样式和 DOM 的隔离,确保组件内部逻辑不受外部影响,适合跨团队协作‌。
  2. 跨框架复用
    原生支持特性使其可在 Vue、React 等框架中直接使用,避免重复开发不同技术栈的组件库‌。
  3. 原生兼容性
    不依赖第三方框架,浏览器原生支持,减少项目维护成本和版本升级风险‌。
  4. 生命周期管理
    提供 connectedCallback(挂载)、disconnectedCallback(卸载)等钩子函数,支持组件状态管理‌。

1.2 兼容性

兼容性
  • Chrome、Firefox、Safari 及 Edge 的最新版本均原生支持 Web Component 的三大核心规范(Custom Elements、Shadow DOM、HTML Templates)‌
  • IE 与旧版浏览器‌:需通过 Polyfill 实现兼容(如 @webcomponents/webcomponentsjs)‌
使用 Polyfill 提高兼容性

对于不完全支持 Web Component 的浏览器,可以使用 Polyfill 库来提高兼容性。

  • webcomponents.js:这是一个由 Google 维护的库,提供了对 Web Components 标准的支持。
npm install @webcomponents/webcomponentsjs
// index.js 或 main.js
import '@webcomponents/webcomponentsjs/webcomponents-loader.js';
  • 通过 CDN 引入:
<script src="https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>

2、Custom Elements (自定义元素)

Custom Elements 允许你定义和使用新的自定义 HTML 标签。你可以通过 JavaScript 创建这些自定义元素,并为它们添加特定的行为和功能。

2.1 自定义元素的类型

  • 自定义内置元素(Customized built-in element)继承自标准的 HTML 元素,例如 HTMLImageElementHTMLParagraphElement。它们的实现定义了标准元素的行为。
<script>
  // 重写内置元素
  // 自定义img标签
  class MyImg extends HTMLImageElement {
    constructor() {
      super();
    }
  }
  // 自定义p标签
  class MyP extends HTMLParagraphElement {
    constructor() {
      super();
    }
  }
</script>
  • 独立自定义元素(Autonomous custom element)继承自 HTML 元素基类 HTMLElement。你必须从头开始实现它们的行为。
<script>
  // 重新定义一个新元素
  class MyDom extends HTMLElement {
    constructor() {
      super();
    }
  }
</script>

2.2 注册自定义元素及使用

要使自定义元素在页面中可用,请调用 Window.customElementsdefine() 方法。

  • 自定义内置元素
<body>
  <img src="https://www.baidu.com/img/flexible/logo/pc/result.png" alt="" />
  <!-- 第三步:使用内置元素,但将自定义名称作为 is 属性的值 -->
  <img is="my-img" src="https://www.baidu.com/img/flexible/logo/pc/result.png" alt="" />

  <script>
    // 第一步:自定义内置元素
    class MyImg extends HTMLImageElement {
      constructor() {
        super();
        this.style.backgroundColor = "red";
      }
    }
    // 第二步:注册,要传对应的内置元素名 { extends: "img" }
    customElements.define("my-img", MyImg, { extends: "img" });
  </script>
</body>

在这里插入图片描述

  • 独立自定义元素(Autonomous custom element)继承自 HTML 元素基类 HTMLElement。你必须从头开始实现它们的行为。
<body>
  <!-- 第三步:直接使用自定义元素作为标签 -->
  <my-dom>121212121</my-dom>

  <script>
    // 第一步:自定义元素
    class MyDom extends HTMLElement {
      constructor() {
        super();
        this.style.backgroundColor = "red";
        this.style.color = "white";
      }
    }
    // 第二步:注册
    customElements.define("my-dom", MyDom);
  </script>
</body>

在这里插入图片描述

注意:

define() 方法接受以下参数:

  • name
    元素的名称。必须以小写字母开头,包含一个连字符,并符合规范中有效名称的定义中列出的一些其他规则。
  • constructor
    自定义元素的构造函数。
  • options
    仅对于自定义内置元素,这是一个包含单个属性 extends 的对象,该属性是一个字符串,命名了要扩展的内置元素。
    无效示例

2.3 自定义元素生命周期

constructor()
  • 描述: 构造函数是自定义元素的初始化方法。
  • 调用时机: 当自定义元素实例被创建时调用。
  • 注意事项:
    • 必须调用 super() 来确保父类的构造函数被调用。
    • 不应该在这个方法中操作 DOM,因为此时元素还没有被插入到文档中。
connectedCallback()
  • 描述: 每当元素被插入到文档中时调用。
  • 用途: 通常用于设置初始状态、监听事件或进行其他需要在元素插入文档后执行的操作。
disconnectedCallback()
  • 描述: 每当元素从文档中移除时调用。
  • 用途: 通常用于清理资源、移除事件监听器或进行其他需要在元素移除文档前执行的操作。
attributeChangedCallback(name, oldValue, newValue)
  • 描述: 每当自定义元素监听的属性observedAttributes发生变化时调用。
  • 参数:
    • name: 发生变化的属性名称。
    • oldValue: 属性的旧值。
    • newValue: 属性的新值。
  • 用途: 通常用于响应属性变化并更新元素的状态或行为。
// 监听的属性
static observedAttributes = ["color", "size"];
// 或者写成get函数
static get observedAttributes() {
  return ["color", "size"];
}
// 回调函数
attributeChangedCallback(name, oldValue, newValue) {
  console.log("自定义元素属性变化",name,oldValue,newValue);
}
  • 使用:
<body>
  <my-dom color="red" size="16">121212121</my-dom>
  <button onclick="document.querySelector('my-dom').setAttribute('color','gary')">按钮</button>

  <script>
    class MyDom extends HTMLElement {
      static get observedAttributes() {
        return ["color", "size"];
      }
      constructor() {
        super();
        console.log("自定义元素创建",this.getAttribute('color'),this.getAttribute('size'));
      }
      connectedCallback() {
        console.log("自定义元素添加至页面",this.getAttribute('color'),this.getAttribute('size'));
      }
      attributeChangedCallback(name, oldValue, newValue) {
        console.log("自定义元素属性变化",name,oldValue,newValue);
      }
    }
    customElements.define("my-dom", MyDom);
  </script>
</body>

在这里插入图片描述

  • 在元素创建时,初始化属性也会触发attributeChangedCallback
  • 当重新设置属性值时,即使新旧值一样,也会触发attributeChangedCallback
adoptedCallback(oldDocument, newDocument)
  • 描述: 每当元素被移动到一个新的文档时调用。
  • 参数:
    • oldDocument: 元素原来所在的文档。
    • newDocument: 元素现在所在的文档。
  • 用途: 通常用于处理跨文档移动的情况,例如移动到iframe中。
<body>
  <my-dom id="mydom">121212121</my-dom>
  <button id="btn">按钮</button>

  <script>
    class MyDom extends HTMLElement {
      constructor() {
        super();
        console.log("自定义元素创建");
      }
      connectedCallback() {
        console.log("自定义元素添加至页面");
      }
      adoptedCallback(oldDocument, newDocument) {
        console.log("自定义元素被移动到新文档",oldDocument, newDocument);
      }
    }
    customElements.define("my-dom", MyDom);

    document.querySelector("#btn").addEventListener("click", ()=> {
      let mydom = document.querySelector("#mydom");
      // 创建一个新的文档
      let newDoc = document.implementation.createHTMLDocument("newDoc");
      // 移动到新文档
      newDoc.adoptNode(mydom);
    })
  </script>
</body>

在这里插入图片描述

3、Shadow DOM (影子DOM)

影子 DOM(Shadow DOM)允许你将一个 DOM 树附加到一个元素上,并且使该树的内部对于在页面中运行的 JavaScript 和 CSS 是隐藏的。这个子树与主文档的 DOM 树是隔离的,这意味着样式和脚本在 Shadow DOM 内部是独立的,不会影响到主文档,反之亦然。这种隔离性使得组件可以更好地封装其内部结构和样式,从而提高代码的可维护性和复用性。

  • 在宿主元素上调用attachShadow(),创建一个Shadow DOM并返回Shadow DOM根对象(类似文档对象document)

3.1 attachShadow()

  • mode决定了外部代码是否可以访问 Shadow DOM 树对象
  • 当创建了Shadow DOM,宿主元素的内容就无效了,(ps:可以使用插槽渲染这些内容)
  • attachShadow({ mode: 'open' })可以访问 Shadow DOM 树
<body>
  <div id="host">host</div>
  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    console.log(shadowRoot, host.shadowRoot);
  </script>
</body>

在这里插入图片描述

  • attachShadow({ mode: 'closed' })不可以访问 Shadow DOM 树

创建时返回的Shadow DOM根对象还可以使用,但是无法重新从宿主元素上获取。

<body>
  <div id="host">host</div>
  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'closed' });
    console.log(shadowRoot, host.shadowRoot);
  </script>
</body>

3.2 Shadow DOM的内容

插入元素
<body>
  <div id="host">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    
    let h1 = document.createElement('h1');
    h1.innerText = 'Web Component';
    shadowRoot.appendChild(h1);
  </script>
</body>
innerHTML插入一段html内容
<body>
  <div id="host">host</div>
  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    
    shadowRoot.innerHTML = `
      <h1>title</h1>
      <div>content</div>
    `;
  </script>
</body>
插入样式
<body>
  <div id="host">host</div>
  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    
    shadowRoot.innerHTML = `
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" />
      <style>
        h1 {
          color: red;
        }
      </style>
      <h1>title</h1>
      <div>content</div>
    `;
  </script>
</body>
插入html文档
<body>
  <div id="host">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    // 这里创建了一个文档来添加,或者你可以加载一个html文件来添加
    shadowRoot.appendChild(document.implementation.createHTMLDocument('shadow').documentElement);
  </script>
</body>

3.3 JavaScript隔离

  • 无法通过主文档document直接获取Shadow DOM的元素
  • 可以通过Shadow DOM根对象获取Shadow DOM的元素
<body>
  <span>主文档</span>
  <div id="host">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `<span>Shadow Dom</span>`;

    console.log('document',...document.querySelectorAll('span'));
    console.log('shadowRoot',...shadowRoot.querySelectorAll('span'));
  </script>
</body>

3.4 CSS隔离

主文档样式无法影响Shadow Dom的样式,反之亦然;
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Web Component</title>
  <style>
    span {
      color: red;
    }
  </style>
</head>

<body>
  <span>主文档</span>
  <div id="host">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <style>
        span{
          color: blue;
        }
      </style>
      <span>Shadow Dom</span>
    `;
  </script>
</body>
</html>

主文档样式和Shadow Dom的样式都可以影响宿主元素
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Web Component</title>
  <style>
    #host{
      width: 200px;
      border: 2px solid red;
    }
  </style>
</head>

<body>
  <span>主文档</span>
  <div id="host">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    // :host 选择器代表宿主元素
    shadowRoot.innerHTML = `
      <style>
        :host{
          width: 100px;
          height: 100px;
          background-color: green;
        }
      </style>
      <span>Shadow Dom</span>
    `;
  </script>
</body>
</html>

:host选中当前的宿主元素
<script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <style>
        :host{
          width: 100px;
          height: 100px;
          background-color: green;
        }
      </style>
      <span>Shadow Dom</span>
    `;
  </script>
:host()当宿主元素符合特定选择器时的样式
<body>
  <span>主文档</span>
  <div id="host" class="a">host</div>

  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <style>
        // 当宿主元素id为host时的样式
        :host(#host){
          width: 100px;
        }
        // 当宿主元素class为a时的样式
        :host(.a){
          height: 100px;
        }
        // 当宿主元素class为b时的样式
        :host(.b){
          background: red;
        }
        // 当宿主元素d为host并且class为a时的样式
        :host(#host.a){
          background: greenyellow;
        }
      </style>
      <span>Shadow Dom</span>
    `;
  </script>
</body>

:host-context()当宿主元素的父元素符合特定选择器时宿主元素的样式
<body>
  <span>主文档</span>
  <div class="par1">
    <div class="par2">
      <div id="host">host</div>
    </div>
  </div>
  
  <script>
    let host = document.getElementById('host');
    let shadowRoot = host.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <style>
        // 当宿主元素的父元素class为par1时,宿主元素的样式生效
        :host-context(.par1) {
          color: red;
        }
        // 当宿主元素的父元素class为par2时,宿主元素的样式生效
        :host-context(.par2) {
          font-size: 20px;
        }
      </style>
      <span>Shadow Dom</span>
    `;
  </script>
</body>

4、HTML Templates (HTML模板)

4.1 <template>HTML模板

核心:通过 <template> 标签定义‌可复用的 HTML 结构片段‌,其内容在页面初始加载时‌不会直接渲染‌,需通过 JavaScript 动态激活后插入到 DOM 中‌。

惰性加载与内容封装‌

<template> 标签内的 HTML 和 CSS 会被浏览器解析但‌不渲染‌,避免资源浪费,同时保持内部结构的独立性‌。

<body>
  <span>主文档</span>
  <template id="my-template">
    <p>template</p>
  </template>
</body>

‌动态内容填充‌

通过 document.querySelector 获取模板,结合 cloneNode(true) 方法克隆内容,支持动态插入数据(如修改属性、绑定事件)‌。

<body>
  <span>主文档</span>
  <div id="box"></div>
  <template id="my-template">
    <p>template 1</p>
    <p>template 2</p>
  </template>

  <script>
    let template = document.getElementById("my-template");
    let content = template.content.cloneNode(true);
    content.querySelector("p").innerHTML = "template cloneNode";
    document.getElementById("box").appendChild(content);
  </script>
</body>

‌作用域隔离‌

模板内部的脚本(<script>)和样式(<style>)在激活前‌不会执行‌,确保逻辑与全局环境隔离‌。

  • 未激活前,不影响全局样式,不执行脚本
<body>
  <span>主文档</span>
  <p class="text">主文档 p</p>
  <div id="box"></div>
  
  <template id="my-template">
    <style>.text { color: red; }</style>
    <p class="text">template 1</p>
    <p>template 2</p>
    <script>
      document.querySelectorAll('p.text').forEach(p => p.textContent += '动态激活');
    </script>
  </template>
</body>

  • 插入其他元素,激活后,影响全局样式,执行脚本
<body>
  <span>主文档</span>
  <p class="text">主文档 p</p>
  <div id="box"></div>
  
  <template id="my-template">
    <style>.text { color: red; }</style>
    <p class="text">template 1</p>
    <p>template 2</p>
    <script>
      document.querySelectorAll('p.text').forEach(p => p.textContent += '动态激活');
    </script>
  </template>
  <script>
    let template = document.getElementById("my-template");
    let content = template.content.cloneNode(true);
    document.getElementById("box").appendChild(content);
  </script>
</body>

4.2 <slot>插槽

  • 占位符机制
    Slot 是 Web Components 中用于在组件内部‌预留内容区域‌的占位符,允许外部在使用组件时插入自定义内容,实现‌动态内容分发‌。‌12
  • Shadow DOM 依赖
    单独使用Slot没有意义,Slot 必须与 Shadow DOM 结合使用,通过封装性隔离组件内部结构,同时借助插槽建立与外部 DOM 的内容连接通道。‌
<body>
  <span>主文档</span>
  <div id="box">
    <span>span1</span>
    <span slot="title">title</span>
    <span>span2</span>
  </div>
  <template id="my-template">
    <p>默认插槽: <slot>默认插槽</slot></p>
    <p>具名插槽: <slot name="title">具名插槽</slot></p>
  </template>
  
  <script>
    let template = document.getElementById("my-template");
    let content = template.content.cloneNode(true);
    document.getElementById("box").attachShadow({ mode: "open" }).appendChild(content);
  </script>
</body>

这里是复制模版内容到shadow dom,也可以直接设置shadow dom的内容innerHTML。

5、使用示例-自定义输入框

5.1 创建自定义元素类CustomInput

  • 有两个自定义属性,value placeholder
<script>
  class CustomInput extends HTMLElement {
    static get observedAttributes() {
      return ["value", "placeholder"];
    }
    constructor() {
      super();
    }
  }
  customElements.define("custom-input", CustomInput);
</script>

5.2 创建Shadow Dom并添加输入框样式内容

  • contenteditable属性可以让元素内容可编辑
<script>
  class CustomInput extends HTMLElement {
    static get observedAttributes() {
      return ["value", "placeholder"];
    }
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = this.createElementContent(this.getAttribute("value"), this.getAttribute("placeholder"));
    }
    // 生成元素内容
    createElementContent(value,placeholder) {
      return `
        <style>
          #box {
            width: 400px;
            height: 40px;
            line-height: 40px;
            border: 1px solid #d9d9d9;
            border-radius: 4px;
            padding: 0 10px;
            box-sizing: border-box;
            position: relative;
          }
          .placeholder {
            color: #999;
            font-size: 14px;
            position: absolute;
            left: 10px;
            z-index: 0;
          }
          .value {
            color: #333;
            height: 100%;
            outline: none;
            position: relative;
            z-index: 1;
          }
        </style>
        <div id="box">
          <div class="placeholder">${placeholder}</div>
          <div class="value" contenteditable>${value}</div>
        </div>
      `;
    }
  }
  customElements.define("custom-input", CustomInput);
</script>

5.3 监听输入内容变化

<script>
  class CustomInput extends HTMLElement {
    static get observedAttributes() {
      return ["value", "placeholder"];
    }
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = this.createElementContent(this.getAttribute("value"), this.getAttribute("placeholder"));
    }
    // ....
    
    // 自定义元素添加至页面后触发回调
    connectedCallback() {
      // 监听输入框内容变化修改属性值
      this.shadowRoot.querySelector(".value").addEventListener("keyup", (e)=>{
        let input = e.target.innerText;
        if(input!==this.getAttribute("value")){
          this.setAttribute("value",input.trim());
        }
      });
    }
  }
  customElements.define("custom-input", CustomInput);
</script>

5.4 value变化时处理样式和事件

<script>
  class CustomInput extends HTMLElement {
    static get observedAttributes() {
      return ["value", "placeholder"];
    }
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.innerHTML = this.createElementContent(this.getAttribute("value"), this.getAttribute("placeholder"));
    }
    // ....
    
    // 自定义元素属性变化时触发回调
    attributeChangedCallback(name, oldValue, newValue) {
      if(name==="value"&&newValue!==oldValue){
        // 有值时,背景色为白色,遮挡placeholder
        this.shadowRoot.querySelector(".value").style.background = newValue?"#fff":"transparent";
        // 触发自定义change事件
        this.dispatchEvent(new CustomEvent("change",{detail:{value:newValue}}));
      }
    }
  }
  customElements.define("custom-input", CustomInput);
</script>

5.5 使用自定义输入框并监听change事件

<custom-input id="input" value="" placeholder="请输入"></custom-input>
<script>
  document.getElementById("input").addEventListener("change", (e)=>{
    console.log('change',e.detail.value);
  });
</script>

5.6 完整代码

<body>
  <h1>主文档</h1>
  <custom-input id="input" value="" placeholder="请输入"></custom-input>
  
  <script>
    class CustomInput extends HTMLElement {
      static get observedAttributes() {
        return ["value", "placeholder"];
      }
      constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.innerHTML = this.createElementContent(this.getAttribute("value"), this.getAttribute("placeholder"));
      }
      // 生成元素内容
      createElementContent(value,placeholder) {
        return `
          <style>
            #box {
              width: 400px;
              height: 40px;
              line-height: 40px;
              border: 1px solid #d9d9d9;
              border-radius: 4px;
              padding: 0 10px;
              box-sizing: border-box;
              position: relative;
            }
            .placeholder {
              color: #999;
              font-size: 14px;
              position: absolute;
              left: 10px;
              z-index: 0;
            }
            .value {
              color: #333;
              height: 100%;
              outline: none;
              position: relative;
              z-index: 1;
            }
          </style>
          <div id="box">
            <div class="placeholder">${placeholder}</div>
            <div class="value" contenteditable>${value}</div>
          </div>
        `;
      }
      // 自定义元素添加至页面后触发回调
      connectedCallback() {
        // 监听输入框内容变化修改属性值
        this.shadowRoot.querySelector(".value").addEventListener("keyup", (e)=>{
          let input = e.target.innerText;
          if(input!==this.getAttribute("value")){
            this.setAttribute("value",input.trim());
          }
        });
      }
      // 自定义元素属性变化时触发回调
      attributeChangedCallback(name, oldValue, newValue) {
        if(name==="value"&&newValue!==oldValue){
          // 有值时,背景色为白色,遮挡placeholder
          this.shadowRoot.querySelector(".value").style.background = newValue?"#fff":"transparent";
          // 触发自定义change事件
          this.dispatchEvent(new CustomEvent("change",{detail:{value:newValue}}));
        }
      }
    }
    customElements.define("custom-input", CustomInput);
  </script>
  <script>
    document.getElementById("input").addEventListener("change", (e)=>{
      console.log('change',e.detail.value);
    });
  </script>
</body>

6、穿透隔离机制

6.1 CSS 样式穿透

CSS变量注入

利用 CSS 自定义属性(CSS Variables)传递值到 Shadow DOM,实现动态样式控制‌。

/* 外部定义变量 */
:root { --primary-color: #42b983; }
/* 组件内部使用变量 */
:host { color: var(--primary-color); }
::part伪元素样式
  • 在shadow dom 中声明可穿透的元素
  • 在外部使用::part()可设置对应元素的样式
<body>
  <style>
    #box::part(content) {
      color: red;
    }
    /* 无效,无法设置子元素 */
    #box::part(content) p{
      font-weight: bold;
    }
  </style>
  <custom-dom id="box"></custom-dom>
  
  <script>
    class CustomDom extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.innerHTML = `
          <p>我是自定义标签</p>
          <div part="content">
            可穿透样式‌的元素
            <p>子级元素‌</p>
          </div>
          <p part="content">可穿透样式‌的元素</p>
        `;
      }
    }
    customElements.define("custom-dom", CustomDom);
  </script>
</body>

6.2 JavaScript 操作穿透

Shadow Root 访问‌

当 Shadow DOM 模式为 open 时,可通过 element.shadowRoot 直接访问内部 DOM 树‌:

const component = document.querySelector('my-element');
const innerDiv = component.shadowRoot.querySelector('.inner'); // 操作内部元素
‌Closed 模式破解‌

若组件使用 { mode: 'closed' },可通过在构造函数中保留引用实现间接访问‌:

class ClosedComponent extends HTMLElement {
  constructor() {
    super();
    this._shadowRoot = this.attachShadow({ mode: 'closed' });
    this._shadowRoot.innerHTML = `<p>我是自定义标签</p>`;
  }
}
//外部获取保留的引用,操作内部元素
document.querySelector("my-element")._shadowRoot.querySelectorAll("p");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值