首页 / HTML5 入门教程 / Web Components 自定义元素

HTML5 入门教程

Web Components 自定义元素

本教程共 110 篇 · 第 97 篇 · 更新于 2026-07-28 · 约 5 分钟阅读

HTML5HTML5 入门教程Web Components自定义元素Shadow DOM模板

97. Web Components 自定义元素

本节目标:了解 Web Components 是什么,学会创建自定义 HTML 标签,理解 Shadow DOM 的样式隔离作用。

什么是 Web Components

Web Components 是一套浏览器原生技术,让你能创建可复用的自定义 HTML 标签

三大核心技术:

  1. Custom Elements:定义新标签
  2. Shadow DOM:样式和 DOM 隔离
  3. HTML Templates:定义可复用的 HTML 片段
Note

Web Components 是原生技术,不依赖任何框架。Vue、React 组件是框架级的,Web Components 是浏览器级的。

创建自定义元素

最简示例

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.innerHTML = "<button>我是自定义按钮</button>";
  }
}

customElements.define("my-button", MyButton);

定义后就能在 HTML 里用:

<my-button></my-button>

带属性的自定义元素

class UserCard extends HTMLElement {
  constructor() {
    super();
  }

  // 监听哪些属性变化
  static get observedAttributes() {
    return ["name", "avatar"];
  }

  // 属性变化时触发
  attributeChangedCallback(name, oldVal, newVal) {
    this.render();
  }

  // 元素插入 DOM 时触发
  connectedCallback() {
    this.render();
  }

  render() {
    const name = this.getAttribute("name") || "匿名";
    const avatar = this.getAttribute("avatar") || "default.png";
    this.innerHTML = `
      <div class="card">
        <img src="${avatar}" width="50">
        <span>${name}</span>
      </div>
    `;
  }
}

customElements.define("user-card", UserCard);

使用:

<user-card name="张三" avatar="zhang.png"></user-card>
<user-card name="李四"></user-card>

Shadow DOM:样式隔离

Shadow DOM 让组件内部的样式不影响外部,外部样式也不影响内部。

class FancyBox extends HTMLElement {
  constructor() {
    super();

    // 创建 Shadow DOM(closed 或 open)
    const shadow = this.attachShadow({ mode: "open" });

    shadow.innerHTML = `
      <style>
        /* 这里的样式只在这个组件内生效 */
        .box {
          border: 2px solid #3498db;
          padding: 20px;
          border-radius: 8px;
          background: #ebf5fb;
        }
      </style>
      <div class="box">
        <slot></slot> <!-- 插槽:显示标签内的内容 -->
      </div>
    `;
  }
}

customElements.define("fancy-box", FancyBox);

使用:

<fancy-box>这里的内容会显示在插槽里</fancy-box>
Tip

Shadow DOM 的样式隔离是双向的。组件内部样式不影响外部,外部 CSS 也进不去(除了 CSS 变量和 ::part() 伪元素)。

HTML Templates

<template> 标签里的 HTML 不会渲染,等 JS 激活后才用。

<template id="my-tpl">
  <style>
    .greeting { color: blue; font-size: 20px; }
  </style>
  <div class="greeting">你好,<span class="name"></span>!</div>
</template>

<script>
  class GreetingBox extends HTMLElement {
    constructor() {
      super();
      const tpl = document.getElementById("my-tpl");
      const content = tpl.content.cloneNode(true);
      content.querySelector(".name").textContent = this.getAttribute("name");
      this.appendChild(content);
    }
  }
  customElements.define("greeting-box", GreetingBox);
</script>

使用:

<greeting-box name="世界"></greeting-box>

生命周期回调

自定义元素有几个关键的生命周期钩子:

回调触发时机
constructor元素创建时
connectedCallback元素插入 DOM 时
disconnectedCallback元素从 DOM 移除时
attributeChangedCallback监听的属性变化时
adoptedCallback元素被移到新文档时
class LifeCycle extends HTMLElement {
  constructor() {
    super();
    console.log("1. 创建");
  }
  connectedCallback() {
    console.log("2. 插入页面");
  }
  disconnectedCallback() {
    console.log("3. 从页面移除");
  }
  static get observedAttributes() {
    return ["value"];
  }
  attributeChangedCallback(name, old, val) {
    console.log(`4. ${name} 从 ${old} 变成 ${val}`);
  }
}

实际用途

  • 跨框架组件库:一套组件在 Vue、React、Angular 里都能用
  • 设计系统:公司级 UI 组件,不依赖特定框架
  • 微前端:不同团队用不同框架,通过 Web Components 集成
Warning

Web Components 虽然强大,但生态不如 Vue/React 成熟。事件系统、表单集成、SSR 等方面还有坑。大型项目用框架组件更省心,跨框架共享组件时再考虑 Web Components。