Help us understand the problem. What is going on with this article?

Web Componentsの構成要素まとめ(基本編)

More than 5 years have passed since last update.

Web Components の構成要素の特徴についてのまとめ

構成要素

  1. Custom Elements
  2. Template
  3. Shadow DOM
  4. HTMLImports

Custom Elements

  • 任意の名前の要素を作成できる。
  • 要素名には-が必須
<script>
var proto = Object.create(HTMLElement.prototype);
document.registerElement('my-element', {prototype: proto});
</script>

<my-element></my-element>

Template

  • <template>を使ってテンプレートを作成できる。
  • <template>内に記述した内容はDOMとして認識されないため、JSの実行やCSSの読み込みは起こらない。
  • 別のDOM要素内に取り込まれて初めてレンダリングされる。
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var template = document.getElementById('thisIsTemplate');
  var templateClone = document.importNode(template.content, true);
  this.appendChild(templateClone);
}
document.registerElement('this-is-template', {prototype: proto});
</script>
<template id="thisIsTemplate">
<div>This is Template</div>
</template>

<this-is-template></this-is-template>

Shadow DOM

  • カプセル化した要素を作成できる。
  • JSやCSSの影響を限定できる。
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var root = this.createShadowRoot();
  root.innerHTML = '<div class="shadow">This is shadow</div>';
}
document.registerElement('this-is-shadow', {prototype: proto});
</script>

<this-is-shadow></this-is-shadow>

HTML Import

  • 外部HTMLファイルの要素を取り込むことができる。
index.html
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var link = document.querySelector('link[href="import.html"]');
  var template = link.import.getElementById('thisIsTemplate');
  var templateClone = document.importNode(template.content, true);
  var root = this.createShadowRoot();
  root.appendChild(templateClone);
}
document.registerElement('this-is-import', {prototype: proto});
</script>

<this-is-import></this-is-import>
import.html
<template id="thisIsTemplate">
  <div>This is Import</div>
</template>

基本的な使い方

プロトタイプの生成

var poroto = Object.create(HTMLElement.prototype);
  • 要素のプロトタイプをインスタンス化
  • 新しいを要素を作成する場合、HTMLElement.prototypeを渡す。
  • 既存の要素を拡張する場合、HTML____Element.prototype(目的の要素インターフェースのプロトタイプ)を渡す。

コールバック

var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {...}

コールバックは下記の4つがある。

コールバック名 タイミング
createdCallback 要素のインスタンスが作られた時
attachedCallback インスタンスがドキュメントに追加された時
detachedCallback インスタンスがドキュメントから取り除かれた時
attributeChangedCallback(attrName, oldVal, newVal) 属性が追加、削除、更新された時

テンプレートのインポート

var template = document.getElementById('template');
var templateClone = document.importNode(template.content. true);

Shadow DOM の使用

proto.createdCallback = function() {
  ...
  var root = this.createShadowRoot();
}
  • thisprotoを取り込む要素
  • つまりdocument.registerElement()の第一引数にセットする要素

新規要素の生成

document.registerElement('my-element', {prototype: proto});

既存要素の拡張

<script>
var proto = Object.create(HTMLButtonElement.prototype);
proto.createdCallback = function() {
  this.addEventListener('click', function() {
    alert('alert-button is clicked!');
  });
}
document.registerElement('alert-button', {
  prototype: proto,
  extends: 'button'
});
</script>
<button is="alert-button"></button>

まとめ

  • 要素の元(Object.create())に<template>の内容を取り込んで、その元を自分の好きな要素名にして(document.registerElement())表示する。
  • 「コンポーネントのカプセル化」を実現するためには、ShadowRootを生成してそこに要素を取り込む。
<html>
  <head>
    <style>
      div {
        color: red;
      }
    </style>
    <link rel="import" href="import.html">
    <link rel="import" href="library.html">
    <script src="platform.js"></script>
    <script>
      // Create Elementsの機能で新しい要素を生成
      var proto = Object.create(HTMLElement.prototype);
      proto.createdCallback = function() {
        this.innerHTML = '<div>This is normal</div>';
      }
      document.registerElement('this-is-normal', {prototype: proto});

      // Shadow DOMの機能を使って要素をカプセル化
      // CSSがShadow DOM要素内のみに適用される。
      var protoShadow = Object.create(HTMLElement.prototype);
      protoShadow.createdCallback = function() {
        var root = this.createShadowRoot();
        root.innerHTML = '<div class="shadow"><style>div {color: red;}</style>This is shadow</div>';
      }
      document.registerElement('this-is-shadow', {prototype: protoShadow});


      // Template機能を使ってテンプレート要素を読み込む。
      // カプセル化されていないなので、appendChildで取り込まれるとCSSが全体に影響する。
      var protoTemplate = Object.create(HTMLElement.prototype);
      protoTemplate.createdCallback = function() {
        var template = document.getElementById('thisIsTemplate');
        var templateClone = document.importNode(template.content, true);
        this.appendChild(templateClone);
      }
      document.registerElement('this-is-template', {prototype: protoTemplate});

      // TemplateとShadow DOM機能を使ってテンプレート内容をカプセル化
      // テンプレート内の要素にのみCSSが適用される。
      var protoShadowTemplate = Object.create(HTMLElement.prototype);
      protoShadowTemplate.createdCallback = function() {
        var template = document.getElementById('thisIsShadowTemplate');
        var templateClone = document.importNode(template.content, true);
        var root = this.createShadowRoot();
        root.appendChild(templateClone);
      }
      document.registerElement('this-is-shadow-template', {prototype: protoShadowTemplate});

      // HTML Imports機能を使って外部HTMLファイルのテンプレート内容を取り込む。
      var protoImportShadowTemplate = Object.create(HTMLElement.prototype);
      protoImportShadowTemplate.createdCallback = function() {
        var link = document.querySelector('link[href="import.html"]');
        var template = link.import.getElementById('thisIsImportShadowTemplate');
        var templateClone = document.importNode(template.content, true);
        var root = this.createShadowRoot();
        root.appendChild(templateClone);
      }
      document.registerElement('this-is-import-shadow-template', {prototype: protoImportShadowTemplate});

      // 外部HTMLファイルのテンプレート内にライブラリのリンクをまとめて記述しておけば一括で取り込める。
      var protoImportLibrary = Object.create(HTMLElement.prototype);
      protoImportLibrary.createdCallback = function() {
        var link = document.querySelector('link[href="library.html"]');
        var template = link.import.getElementById('thisIsImportLibrary');
        var templateClone = document.importNode(template.content, true);
        this.appendChild(templateClone);
      }
      document.registerElement('this-is-import-library', {prototype: protoImportLibrary});
    </script>

    <template id="thisIsTemplate">
      <style>div {color: blue;}</style>
      <div>This is Template</div>
    </template>

    <template id="thisIsShadowTemplate">
      <style>div {color: green;}</style>
      <div class="shadow">This is TemplateShadow</div>
    </template>

  </head>
  <body>
    <this-is-normal></this-is-normal>
    <this-is-shadow></this-is-shadow>
    <this-is-template></this-is-template>
    <this-is-shadow-template></this-is-shadow-template>
    <this-is-import-shadow-template></this-is-import-shadow-template>
    <this-is-import-library></this-is-import-library>
  </body>
</html>
import.html
<template id="thisIsImportShadowTemplate">
  <style>div {color: gray;}</style>
  <div>This is Import</div>
</template>
library.html
<template id="thisIsImportLibrary">
  <div>This is Import Library</div>
  <link rel="stylesheet" href="font-awesome-4.1.0/css/font-awesome.min.css">
</template>

参考

Web開発者に革命をもたらす!「Web Components」超入門
なぜ Web Components はウェブ開発に革命を起こすのか
Shadow DOM 101
Shadow DOM 201

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした