Web Componentsの作り方

Web Componentsは次の3つの技術を組み合わせることで作れます。👇

  • Custom Elements → タグを作る
  • Shadow DOM → 見た目を守る
  • HTML Templates → 中身を使い回す

👉 この3つを理解すれば、実用的なコンポーネントが作れるようになります。


Custom Elements(カスタムタグ)

役割

👉 独自のHTMLタグを作る

最小サンプル

まずは「タグを作るだけ」

class MyGreeting extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<p>こんにちは!</p>";
  }
}

customElements.define("my-greeting", MyGreeting);

HTMLで使う:

<my-greeting></my-greeting>

ポイント

  • クラスは HTMLElement を継承
  • customElements.define() で登録
  • タグ名には「-(ハイフン)」が必須

👉 例:my-button, user-card

独自サンプル:属性付きコンポーネント

class UserName extends HTMLElement {
  connectedCallback() {
    const name = this.getAttribute("name") || "ゲスト";
    this.innerHTML = `<p>ようこそ、${name}さん!</p>`;
  }
}

customElements.define("user-name", UserName);
<user-name name="山田"></user-name>

👉 HTMLだけで動的なUIが作れる


Shadow DOM(カプセル化)

役割

👉 CSSやHTMLを外部から隔離する。

なぜ必要?

普通のHTMLだと…

p {
  color: red;
}

👉 全部の <p> に影響してしまう。

Shadow DOMを使うことで、Web Components内部だけにCSSの影響を限定できます。

class MyBox extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        p {
          color: blue;
        }
      </style>
      <p>この文字は青だけど外には影響しない</p>
    `;
  }
}

customElements.define("my-box", MyBox);

ポイント

  • attachShadow() で有効化
  • shadowRoot にHTMLを書く
  • 外部CSSの影響を受けない&与えない

👉 CSS地獄を防ぐ最重要機能

独自サンプル:ボタンコンポーネント

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: black;
          color: white;
          padding: 10px;
          border-radius: 5px;
        }
      </style>
      <button>クリック</button>
    `;
  }
}

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

👉 ページのCSSに影響されない安全なボタン


HTML Templates(テンプレート)

役割

👉 HTML構造を使い回す。

なぜ必要?

毎回こう書いたり、

this.shadowRoot.innerHTML = `...長いHTML...`;

こう書くのは面倒:

this.table = document.createElement("table");
this.thead = document.createElement("thead");
this.tbody = document.createElement("tbody");
this.tfoot = document.createElement("tfoot");
this.table.appendChild(this.thead);
this.table.appendChild(this.tbody);
this.table.appendChild(this.tfoot);

👉 HTMLをテンプレートとして分離すると使い回しもできて、見やすくなる。

基本の使い方

<template id="card-template">
  <style>
    .card {
      border: 1px solid #ccc;
      padding: 10px;
    }
  </style>
  <div class="card">
    <slot></slot>
  </div>
</template>

JavaScript側

class MyCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    const template = document.getElementById("card-template");
    const content = template.content.cloneNode(true);
    this.shadowRoot.appendChild(content);
  }
}

customElements.define("my-card", MyCard);

使用例

<my-card>
  <p>カードの中身です</p>
</my-card>

ポイント

  • <template> は画面に表示されない
  • cloneNode(true) でコピーして使う
  • <slot> で中身を差し込める

👉 実務ではほぼ必須レベル


3つを組み合わせた完成形(実践サンプル)

「プロフィールカード」コンポーネント

HTML

<template id="profile-template">
  <style>
    .card {
      border: 1px solid #ddd;
      padding: 10px;
      border-radius: 8px;
    }
    .name {
      font-weight: bold;
    }
  </style>
  <div class="card">
    <p class="name"></p>
    <slot></slot>
  </div>
</template>

<profile-card name="山田太郎">
  <p>エンジニアです</p>
</profile-card>

JavaScript

class ProfileCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    const template = document.getElementById("profile-template");
    const content = template.content.cloneNode(true);

    const name = this.getAttribute("name") || "不明";
    content.querySelector(".name").textContent = name;

    this.shadowRoot.appendChild(content);
  }
}

customElements.define("profile-card", ProfileCard);

ここが重要

この1つのコンポーネントに👇が全部入っています

  • Custom Elements → <profile-card>
  • Shadow DOM → スタイル隔離
  • Template → 再利用可能構造

👉 これがWeb Componentsの完成形です


実務での考え方(かなり重要)

よくある設計パターン

  • 良い設計
  • UI単位で分割
  • 属性でデータ受け渡し
  • slotで柔軟性確保

悪い設計

  • 1コンポーネントが巨大
  • JS依存が強すぎる
  • slotを使わない

まとめ

Web Componentsの作り方はシンプルです👇

  • Custom Elements → タグを作る
  • Shadow DOM → 安全にする
  • Template → 使い回す

👉 この3つを組み合わせるだけ