Web開発者に革命をもたらす!「Web Components」超入門

Web開発者に革命をもたらす!「Web Components」超入門

王

こんにちは。デザイナーの王です。
Webアプリはデスクトップアプリとは違い、まだまだ発展途上の技術のため、色んな所でまだ未熟な部分があります。デスクトップアプリでは当たり前のことでもWebアプリではできなかったりすることも多いのです。中でも、UIのコンポーネント化問題が以前から指摘されてきました。

通販サイトにある「購入ボタン」を例に説明すると分かりやすいと思います。

この手のボタンを作るには以下の手続きを要すると考えられます。

  • 外観を整える
    • CSS
    • HTMLマークアップ
  • クリックした際の挙動
    • JavaScript

何が厄介かというと、「再利用」が難しいというところなんですね。

例えば、同サイトの別のページで同じボタンを使いたい場合、js、CSS、HTMLを再度記述しなければなりません。しかも場合によってはHTMLのマークアップが非常に冗長化していることもある。

「購入ボタン」はあくまで一例にすぎないです。何もUIに限った話ではなく、ウィジェット系もそうですし、およそ再利用できると考えられる部品全般にこのような問題が発生します。

幸い、数年前から、Google(Chromeチーム)はずっとこの問題に取り組んできました。長い間、あまり知られていなかったが、今、時代が動き出した!Googleを筆頭に「Web Components」と言われる技術がようやく日の目を見始めてきました!この技術によって、WebアプリのUI問題が大幅に改善されると予想されます。

今回は「Web Components」について書いてみたいと思います!うまく活用すれば、間違いなく作業率が見間違えるほど激変すると思うので、ぜひ本記事をきっかけに今のうちに「Web Components」を知っておいて頂きたいです!

目次

Web Componentsを構成する5つ仕様

「HTML5」のように、「Web Components」はあくまで1つの概念であって、実際は個々のサブ仕様から構成されています。今後も追加されるかもしれないが、今のところ、以下の5つのパーツ(仕様)から構成されています。

  1. Templates
  2. Decorators
  3. Shadow DOM
  4. Custom Elements
  5. Imports

これらの仕様は連携して使用するように設計されていますが、もちろん必要なパーツだけピックアップして使っても構いません。Decoratorsの仕様はまだ不明瞭なため、本記事ではそれ以外の4つの仕様について説明していきます。

Web Componentsの概要

Googleのトップページにある「検索ボックス」を検証してみると…

p1

外から内へと、幾重にも重なっていて、見ているだけで目眩がしそうな構造ですね…この記述の冗長さを何とかしたいところです!

例えば、こういうふうに、タグ1つで記述できたら大分スッキリしないか?

<google-search-box voice-search="true"></google-search-box>

※仮に「voice-search」属性を使えば、音声検索機能(マイクのアイコン)のオン・オフを切り替えられると仮定します。

こういう時にこそ、Web Componentsの腕の見せ所!!
Web Componentsを駆使して上記の様なことを実現するには、ざっと以下のようなステップが考えられます。

  1. Custom Elementsを使って、新しい要素「google-search-box」を作り、「voice-search」属性を実装する。
  2. Templatesを使って、内部の複雑な要素群、適用するCSSを詰め込む。
  3. Shadow DOMを使って、テンプレートの中身を全部ShadowRootに封じ込み、外部から見えなくする。
  4. Importsを使って、上記手順で作った「Web Components」をファイルからインポートする(オプション)。
  5. HTMLタグを挿入。

Web Componentsを利用するメリット

  • HTMLのマークアップがスッキリする
  • 汚い内部構造を外から見えなくする
  • 内部のスタイルが外部に漏れないと同時に、外部のスタイルも内部に染み込まない
  • 一度新しい要素を作ってしまえば、至る所で再利用できる
  • コンポーネントの流用だけなら、わざわざJavaScriptを書く必要がなくなる

iframeとは似て非なるもの

上記の説明を見て、勘の良い人なら、「これってiframeじゃない?」って思ってくれるでしょう。

要はFacebookの「いいね!」ボタンのように、CSSやjsやHTML、それら全てをカプセル化して、iframe 1つで引用して使っているわけですね。尚且つ、引用する側のページはiframe内のスタイルやjsの影響を受けないというメリットもある。

確かに似ていますよね?だが!考え方としては参考になっても、Web Components は iframeとは似て非なるものです!どこが違うのか、詳しく見ていけば分かって頂けると思いますが、「Web Components」に出来てiframeにできない事として、以下の点が挙げられると思います。

  • HTTPリクエストは発生しない
  • タグ名を自分で定義できる
  • タグの中にさらに別のタグを入れることもできる
  • 独自のAPIを外部に提供することができる

環境を整える

ブラウザサポート

Chrome 31から徐々にWeb Componentsの各パーツを導入し始めていますが、まだ実験段階なので、「体験」するには、chrome://flags/ ページで「Experimental Web Platform features」を有効化する必要があります。

p2

polyfillに関して

また、IEも含めて、Chrome以外のブラウザでも、下記のpolyfillを使用すれば、同様の結果が得られます!実際に各ブラウザでこの技術が普及するまでの間、一時的にpolyfillで我慢する形にはなりますが、こういったライブラリがあるお陰で、長きに渡る仕様検討から最終仕様の決定まで待つ必要がなくなる分、すごく助かりますね!

特にGoogleが推している「Polymer」というライブラリはほんとに素晴らしい!単なるpolyfillだけではなく、データバインディングやアニメーション等のモジュールもあり、Webアプリを作成するための「標準システム」のようなものを目指していて、かなり力を入れているそうです。

Custom Elements

WebページやWebアプリのレイアウトを組む時に、皆さんはどんなタグを使っていますか?

常用のタグとして、div、aside、section、header、footer 等の便利なタグがありますよね。HTMLで規定されている要素は相当の数に上ります。しかし、それでもWebの世界を表す言語として、ひどく語彙不足なのです。

Custom Elements」を使うことで、HTMLの固有要素に縛られることなく、独自の新しい要素を定義することができます。「Web Componentsの概要」のほうでも「google-search-box」という新しいタグを使った架空のサンプルコードにもあったように、「Custom Elements」はWeb Componentsで最も重要なパーツと言っても過言ではないでしょう。

新しいカスタム要素を登録する

まず初めに、document.register() で新しい要素を登録します。

var XFoo = document.register('x-foo');
document.body.appendChild(new XFoo());

構文

document.register(type [, ElementRegistrationOptions ])

引数

type
登録したい新しいカスタム要素の名前(文字列)。
ElementRegistrationOptions
{prototype:object, extends:string}の形を取ったオブジェクトです。’prototype’はカスタム要素のプロトタイプオブジェクトで、’extends’はどの要素を拡張しているかを表す文字列です。いずれもオプションです。

説明

document.register()の第一引数はカスタム要素名です。ここで注意しないといけないのは、要素名には必ず - を含む必要があります(通常のHTML要素と区別するためです)。

以下は有効な要素名の例です。

<x-foo>, <i-element>, <my-book>

以下は無効な要素名の例です。

<foo>, <iElement>, <myBook>

第二引数はオプションで、カスタム要素のプロトタイプ(パブリックメソッドやプロパティ)を設定できるところです。デフォルトでは、HTMLElementを継承することになっているので、前述のサンプルコードは以下に相当します。

var XFoo = document.register('x-foo', {
  prototype: Object.create(HTMLElement.prototype)
});

document.register()を使うことによって、ブラウザは新しい要素「x-foo」が認識できるようになります。戻り値は<x-foo>のコンストラクターで、<x-foo>要素のインスタンスを作ることができます。

要素を拡張する

新しい要素をゼロから作るのもいいのですが、buttonやinput等、既存の要素の上にさらに機能追加して拡張すれば、無駄な手数を省き効率的な開発ができます!

例えば、E-mailのバリデーション機能付きのinput要素を作るとしましょう。

input要素を拡張するには、まず、HTMLInputElementのprototypeを継承した新しい要素を作ります。

var EvInput = document.register('ev-input', {
  prototype: Object.create(HTMLInputElement.prototype)
});

この手の要素は「type extension custom elements」と言って、既存の要素に’is’属性を付けて使います。

<input is="ev-input">

文字通り、 input is ‘ev-input’ という意味合いです。もちろん、これだけではE-mailのバリデーション機能は付きません。機能拡張の仕方は後述します、とりあえずinputの機能を継承するところまで覚えておこう。

マークアップによるカスタム要素の登録について

上記ではjsによるカスタム要素の登録について説明してきましたが、実は<element>を使った「マークアップ」による登録という方法もあります。ただ、現在(2013/11/15)は仕様※現在はサービスを終了しています。だけあって、まだどのブラウザも未実装です。なので、さらっとだけ見ておくことにします。

<element extends="button" name="fancy-button">
    …
</element>

name‘の値は作成するカスタム要素の名前。’extends‘には継承させたい要素の名前を入れる。

<script> 要素を使ってプロパティとメソッドの定義を行う。

<element name="tick-tock-clock">
  <script>
    ({
      tick: function () {
        …
      }
    });
  </script>
</element>

※確かにまだ未実装な機能ですが、Polymerを使えば、マークアップによる実装も可能です。気になる方は creating-a-polymer-element をご参照下さい。

要素のインスタンス化

通常のHTML要素と同じく、カスタム要素も、HTMLでタグ名を記述する「マークアップ型」と、DOMをjsで作る「スクリプト型」の二通りのインスタンス方法があります。

マークアップ型

普通のカスタム要素
<x-foo></x-foo>
type extensionsカスタム要素
<input is="ev-input">

スクリプト型

普通のカスタム要素
var xFoo = document.createElement('x-foo');
document.body.appendChild(xFoo);

newを使う↓

var xFoo = new XFoo();
document.body.appendChild(xFoo);
type extensionsカスタム要素
var evInput = document.createElement('button', 'ev-input');
document.body.appendChild(evInput);

上記のように、Custom Elementsをサポートしているブラウザなら、document.createElement()に、新たに「型」を受け取る二番目の引数を渡すことができます。

newを使う↓

var evInput = new EvInput();
document.body.appendChild(evInput);

プロパティとメソッドの実装

要素を登録しただけでは、あまり意味がないので、プロパティとメソッドの実装も見て行きましょう!

以下サンプルコードです。

/*
    1. HTMLElementのプロトタイプを継承したオブジェクト'XFooProto'を作る。
    このオブジェクトは[ステップ5]で新しいカスタム要素'<x-foo>'のプロトタイプとなるオブジェクトです。
*/
var XFooProto = Object.create(HTMLElement.prototype);


/*
    2. XFooProtoに'foo'というメソッドを追加
*/
XFooProto.foo = function() {
  alert('hello!');
};


/*
    3. 読み取り専用プロパティ "bar" も追加
*/
Object.defineProperty(XFooProto, "bar", {value: 5});


/*
    4. 'XFooProto'オブジェクトを使って、 <x-foo>要素を登録
*/
var XFoo = document.register('x-foo', {prototype: XFooProto});

/*
    5. <x-foo>要素のインスタンスを作る
*/
var xfoo = document.createElement('x-foo');

/*
    6. ページに追加
*/
document.body.appendChild(xfoo);

もちろん、プロトタイプオブジェクトを作るのには色んな方法があります。上記はほんの一例に過ぎません。例えばこういう風にだって記述できます。

var XFoo = document.register('x-foo', {
  prototype: Object.create(HTMLElement.prototype, {
    bar: {
      get: function() { return 5; }
    },
    foo: {
      value: function() {
        alert('foo() called');
      }
    }
  })
});

普段オブジェクトリテラルを常用してて、あまりこの手のオブジェクトの作り方に慣れ親しんでいない方はObject.definePropertyObject.createの使い方を把握しておくといいと思います。

Lifecycle callback methods

通常の要素と違い、カスタム要素には「Lifecycle callback methods」と言われるイベントハンドラーが存在しています。初期化処理など、カスタム要素を作る上で大変重要な概念です。以下そのコールバックの名前といつ発火するかをまとめた表です。

名前 発火時
createdCallback インスタンスを作成した時
enteredViewCallback インスタンスがドキュメントに挿入された時
leftViewCallback インスタンスがドキュメントから削除された時
attributeChangedCallback(attrName, oldVal, newVal) 要素の属性の追加、削除、更新の時

コード例:

var XFoo = document.register('x-foo', {
  prototype: Object.create(HTMLElement.prototype, {
    createdCallback: {
      value: function() {
        alert('Created!');
      }
    }
  })
});

new XFoo(); //'Created!'がアラートされる。HTMLで<x-foo></x-foo>と記述した場合も同様。

コンテンツを追加

これまで、カスタム要素の登録、プロパティとメソッドの追加をしてきましたが、まだ中身が空っぽですよね。

では、試しに、下記の条件が備わったカスタム「ボタン」を作ってみましょう!

  • クリックしたら「クリックされた!」とアラートする
  • ボタンの色は無作為に生成する

※カスタム要素名を<MyButton> とすると、HTML + CSS + js は以下になります。

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <style>
    /*ボタンのスタイルを設定*/
    my-button {
      color: #FFF;
      padding: 1.5em;
      font-family: sans-serif;
      display: inline-block;
      border-left: 10px solid black;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <my-button></my-button>
  <my-button></my-button>
  <my-button></my-button>
  <script>
    var MyButton = document.register('my-button', {
      prototype: Object.create(HTMLElement.prototype, {
        createdCallback: {
          value: function () {
            //イベントハンドラーを設定
            this.addEventListener('click', function (e) {
              alert('クリックされた!')
            });

            //ボタンの文字を設定
            this.innerHTML = "<span>クリックして下さい!</span>";

            //背景色をランダムに生成
            this.style.backgroundColor = randColor();
          }
        }
      })
    });


    //ランダムの色を生成する関数
    function randColor() {
      var color = Math.floor(Math.random() * 0xFFFFFF).toString(16);
      for (var count = color.length; count < 6; count++) {
        color = "0" + color;
      }
      color = "#" + color;
      return color;
    }
  </script>
</body>
</html>

結果、こうなりましたね。クリックすると「クリックされた!」というメッセージが飛び出すはずです。

p4

そして、デベロッパーツールでボタンを検証して見て下さい。以下のような内容となっているはずです。
[/code]

<my-button style="background-color: #eec700;"><span>クリックして下さい!</span></my-button>

Shadow DOMでコンテンツをカプセル化

Shadow DOMについては後で詳しく説明しますが、ここでは前述の「my-button」の例の延長として、一足先に使ってみます!Shadow DOMの概念やCustom Elementsとの連携の仕方、だけでも掴んで頂けたらと思います。

var MyButton = document.register('my-button', {
  prototype: Object.create(HTMLElement.prototype, {
    createdCallback: {
      value: function () {
        //イベントハンドラーを設定
        this.addEventListener('click', function (e) {
          alert('クリックされた!')
        });

        //カスタム要素にshadowRootを作成しておく
        var shadowRoot = this.createShadowRoot();
        //shadowRootの中身を変える
        shadowRoot.innerHTML = "<span>クリックして下さい!</span>";

        //背景色をランダムに生成
        this.style.backgroundColor = randColor();
      }
    }
  })
});

もう一度検証してみると、あれ?あれれ?中身であるクリックして下さい!が消えて、<my-button>だけになりました!Shadow DOMという言葉の通り、まさに「隠された」DOMなのです!

では、中身はどこに隠れているのかというと、お察しの通り、「ShadowRoot」の中に入っているのです!

デベロッパーツールを開いて、右下の歯車アイコンをクリックして、出てきた設定画面でShadowRootの中身を見えるようにするオプションがありますので、チェックします!

p3

そうすると、このように、中身が半透明状に表示されます。

p5

templateを使ってコンテンツを作成

ひとまずコンテンツをカプセルの中に封印したことに成功しましたね!

が!しかし1つ問題が残ります。それは、今の中身は「クリックして下さい!」というごくごく単純の物しか入っておらず、特にこれと言った問題はなさそうに感じますが、実戦で使おうとすると、中身が肥大化するにつれて、どんどんコード量が増え、見た目的にも保守的に宜しくないというわけですね。

そこで「template」の腕の見せ所です!templateについても後で詳しく説明しますが、とりあえずサンプルコードを見て下さい。

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <style>
    /*ボタンのスタイルを設定*/
    my-button {
      color: #FFF;
      padding: 1.5em;
      font-family: sans-serif;
      display: inline-block;
      border-left: 10px solid black;
      cursor: pointer;
    }
  </style>
  <template id="myButtonTemp">
    <style>
      span {
        color: #FFF;
        text-shadow: 0 0 20px, 0 0 20px, 0 0 20px;
      }
    </style>
    <span>クリックして下さい!</span>
  </template>
</head>

<body>
  <my-button></my-button>
  <my-button></my-button>
  <my-button></my-button>
  <p>
    <span>spanです!</span>
  </p>

  <script>
    var MyButton = document.register('my-button', {
      prototype: Object.create(HTMLElement.prototype, {
        createdCallback: {
          value: function () {
            //イベントハンドラーを設定
            this.addEventListener('click', function (e) {
              alert('クリックされた!')
            });

            //カスタム要素にshadowRootを作成しておく
            var shadowRoot = this.createShadowRoot();

            var template = document.querySelector('#myButtonTemp');

            //テンプレートの中身をshadowRootにコピペする
            shadowRoot.appendChild(template.content.cloneNode(true));

            //背景色をランダムに生成
            this.style.backgroundColor = randColor();
          }
        }
      })
    });


     //ランダムの色を生成する関数
    function randColor() {
      var color = Math.floor(Math.random() * 0xFFFFFF).toString(16);
      for (var count = color.length; count < 6; count++) {
        color = "0" + color;
      }
      color = "#" + color;
      return color;
    }
  </script>
</body>
</html>

どこが変わったかというと、新たに「template」という要素を書き加え、「template」の中身をshadowRootにコピペしたというところですね。このやり方なら、以下のメリットが得られます。

  • まとまったコードをカスタム要素に一括挿入できる
  • style要素によるCSSの影響範囲はshadowRootに閉じられ、外に漏れることはない

Shadow DOM

ここまで読み飛ばしていなかった方には、既にある程度分かっているとは思うのですが、このセクションでは、もうちょっと詳しく「Shadow DOM」について説明します。

shadow rootを作成

Shadow DOM仕様の目的は「肥大化したした内部構造」を「隠す」ことにあります。あらゆる要素にはCreateShadowRootというメソッドが付与されています。このメソッドによって、「shadow root」を作成することができます。さらに、shadow rootに挿入したものは全て「隠される」ことになります。また、「shadow root」を有する要素のことを「shadow host」と呼ばれます。

以下はpタグにShadowRootを作成する例です。

<p>foo</p>
<script>
var host = document.querySelector('p');
var root = host.createShadowRoot();
root.textContent = 'Shadow DOM';
</script>

実行すると、もともとpタグに入っていた「foo」が消え、代わりに「Shadow DOM」が表示されていることに注目して下さい。ShadowRootの内容が優先的表示されるからです。

このように、ShadowHostのテキストを変えてしまうこともできるのです!ただ、原則としては、検索エンジンやスクリーンリーダーなどから正しく認識できるよう、ShadowRootのテキストはShadowHostの内容と常に一致する必要がある。

「content」要素を使う

Shadow DOMを扱う際に、新たに追加された要素「content」を使うことで、ShadowHostの中身をShadowRootの中に持ってくることができます。と言われてもピンと来ないと思うので、以下の例で説明します。

例えばこんな記述をしたら

<x-site>LIG</x-site>
<x-site>LIGMO</x-site>
<x-site>LIGLIS</x-site>

こういう風に表示するコンポーネントを作るとします。
p6

コード例

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">

  <template id="template">
    <style>
      /* @host でShadowHost要素を選ぶ */
      @host {
        * {
          display: block;
          width: 200px;
          border:3px solid black;
          text-align: center;
          margin: 10px 0;
          font-family: sans-serif;
          color: gray;
        }
      }
      
      /* ShadowRoot内のh1 */
      h1{
        color: black;
        font-size: 15px;
        display: inline-block;
        font-family: sans-serif;
      }

    </style>
    
    <h1>サイト名:</h1><content></content>
  </template>
</head>

<body>
  <x-site>LIG</x-site>
  <x-site>LIGMO</x-site>
  <x-site>LIGLIS</x-site>

  <script>
    var XSite = document.register('x-site', {
      prototype: Object.create(HTMLElement.prototype, {
        createdCallback: {
          value: function () {
            //カスタム要素にshadowRootを作成しておく
            var shadowRoot = this.createShadowRoot();
            
            //テンプレートの中身をshadowRootにコピペする
            var template = document.querySelector('#template');
            shadowRoot.appendChild(template.content.cloneNode(true));
          }
        }
      })
    });
  </script>
</body>
</html>

見ての通り、<x-site>LIG</x-site>の中身である「LIG」テキストノードを見事にShadowRootに引っ張ってきていることが分かると思います。

content要素のselect属性

以下のコードを見て下さい。

<x-site>
    <p class="url">liginc.co.jp</p>
    <p class="sitename">LIG</p>
</x-site>

そのままtemplateタグの中でcontent要素を使ったら、二つのpタグともShadowRootに「インポート」されることになる。ここで以下のようにselect属性を使って、インポートされる要素をCSSセレクターで「選択」することができます。

<content select="p.sitename"></content>

これならクラス名がsitenameのpタグだけがインポートされることになります。ただ、ShadowHostの直下の要素しか選べません。つまり「p .foo」みたいな子孫セレクタは使えないということですね。

Shadow DOMのスタイリング

場合によっては外部のスタイルをshdow DOMに継承させることもあるでしょう。例えばウィジェットの場合、外部のスタイルの一部を継承することで、自サイトのデザインに近づけ、一体感を演出することが可能になります!

スタイリングの柔軟性を持たせるために、Shadow DOMには以下の二つのプロパティが提供されています。

.resetStyleInheritance
false:継承可能なCSSプロパティは通常通り継承する。デフォルト値です。
true:継承可能なCSSプロパティを初期値にリセットする。
.applyAuthorStyles
false:ユーザーが定義したCSSプロパティを適用する。デフォルト値です。
true:ユーザーが定義したCSSプロパティを無視する。

実例で見てみましょう。

<style>
  h1 {
    color: #cf0056 !important;
    background: #fac100;
    width: 300px;
    text-align: center;
    padding: 1em 1.5em;
    font-size: 18px;
  }
  section {
    color: pink;
  }
</style>
<section>

  <div>
    <h1>Host title</h1>
  </div>
</section>
<script>
  var root = document.querySelector('div').createShadowRoot();
  // root.applyAuthorStyles = true; //デフォルト: false
  // root.resetStyleInheritance = true; //デフォルト: false
  root.innerHTML = '<h1>Shadow DOM Title</h1>';
</script>

今、applyAuthorStyles と resetStyleInheritance を設定している所はコメントアウトしていますが、true/false にして、結果がどう変わるか、下の図解を一度確認したら二つのプロパティの働きを正しく理解してもらえると思います。
style

※継承可能なCSSプロパティをご存知でない方、こちらの記事に一覧が載ってますのでご参照下さい。

content要素のスタイリング

content要素はちょっとした特殊な要素です。というのも、見た目上ではShadowDOMの一部に見えますが、実際は依然としてShadowHost要素の子供要素なのです。このような要素を「Distributed Nodes」と呼びます。
なので、content要素にスタイルを付けようと思うのなら、ShadowDOMの中ではなく、一般の要素と同じく外からCSSをあてればいいです。ただ、外側でCSSをあてるようでは、CSSのカプセル化ができなくなります!だから、内側でCSSをあてる手立てが必要です。それを今から説明します。

::distributed() 擬似セレクターを使う

::distributed() 擬似セレクターを使うことで、Distributed Nodesを選択することができます。以下用例です。

<style>
  h1 {
    color: orange; /*適用されない*/
    background-color: #EACFFF; /*適用される*/
    width: 270px; /*適用される*/
  }

  section {
    color: pink;
  }
</style>

<section>
  <div>
    <h1>Host title</h1>
  </div>
</section>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.innerHTML = '<style>   content::-webkit-distributed(h1) { color: blueviolet; }   </style>' +
                   '<h1>Shadow DOM Title</h1>' +
                   '<content select="h1"></content>'
</script>

表示結果は以下になります。
style2

表示結果を見て分かる通り、::distributed()のほうが優先度が高いとは言え、外からのCSSはやはり適用されていることに注目して下さい。つまるところ、Distributed Nodes はやはりShadowDOMではないので、外側のCSSの適用対象になるのも無理もないということですね。

Templates

<template>要素はその名の通り、テンプレート、或いはスニペットを必要とする時に使う要素です。

アクティブにするまで、ブラウザはtemplateタグ内に書かれたコードは一切評価しません。templateにある画像はロードは発生しないし、スクリプトは実行されない、CSSは適用しない、オーディオは再生されない… 

使い方

template要素は実際に使われなければ、コメントアウト同然のようなものです。多くの場合cloneNode()を使って中身をコピペして使うことが多い。

以下の例では、ボタンをクリックしない限り、script要素は実行されないことを確認してみて下さい。

<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
  function useIt() {
    var content = document.querySelector('template').content;
    // Update something in the template DOM.
    var span = content.querySelector('span');
    span.textContent = parseInt(span.textContent) + 1;
    document.querySelector('#container').appendChild(
        content.cloneNode(true));
  }
</script>

<template>
  <div>Template used: <span>0</span></div>
  <script>alert('Thanks!')</script>
</template>

※templateを入れ子にした場合、外側のtemplateをアクティブにしても、内側のtemplateはアクティブにはならないので、ご注意下さい。

Imports

最後に一番簡単な仕様「Imports」について少々。

使い方は至って簡単

<link rel="import" href="x-foo.html">

link要素のrelを「import」にすれば、外部のファイルをインポートすることができます。他の言語のいうところの「require」や「import」に近いものですね。Web Componentsと併用すれば、煩雑なjsやtemplateを単独のファイルとして保存しておいて、それをこうやってインポートしてやれば、マークアップは格段にスッキリしてきます。

polymerのWeb Componentsを使ってみる

では、実際にGoogleが推し進めているフレームワーク「polymer」のUI要素を使ってみましょう!

以下、レーティングマークUIの例です。

ratings

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">

	<!-- polymer をロード -->
	<script src="polymer/polymer.min.js"></script>

	<!-- Custom Elements をインポート -->
	<link rel="import" href="polymer/polymer-ui-elements/polymer-ui-ratings/polymer-ui-ratings.html">

	<style>
	polymer-ui-ratings{
		display: block !important;
	}
	</style>
</head>
<body>

	<!-- デフォルトでは5つ星 -->
	<polymer-ui-ratings value="3"></polymer-ui-ratings>
	
	<!-- 星数を10にする -->
	<polymer-ui-ratings value="3" count="10"></polymer-ui-ratings>

</body>
</html>

以下、toggle-buttonの例です。

toggle-button

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">

	<!-- polymer をロード -->
	<script src="polymer/polymer.min.js"></script>

	<!-- Custom Elements をインポート -->
	<link rel="import" href="polymer/polymer-ui-elements/polymer-ui-toggle-button/polymer-ui-toggle-button.html">

</head>
<body>

	<!-- デフォルトでは「ON/OFF」 -->
	<polymer-ui-toggle-button></polymer-ui-toggle-button>
	<br>

	<!-- 「オン/オフ」にする -->
	<polymer-ui-toggle-button onCaption="オン" offCaption="オフ"></polymer-ui-toggle-button>
	

</body>
</html>

以上、ほんの一例にすぎませんが参考になれば幸いです。

まとめ・参考記事

以上が Web Components についての簡単な入門でした。皆さんが Web Components に興味を持ってもらえるきっかけになると幸いです!

今成長の真っ只中の仕様ですので、今すぐ実戦で使うというのは少々難しいかと思いますが、Polymerなどのライブラリを利用すれば、仕様が安定するまで待たなくていいという所がまた魅力的ですね!polymerに関してはそれ自体が巨大なフレームワークになっているため、この記事で紹介したらとんでもない長編になっちゃいますので、また次の機会にでも譲ろうと思います。

では、良い Web Components ライフを!

参考記事

Web Components: A Tectonic Shift for Web Development – Google I/O 2013
Web Components in Action – Google I/O 2013
Polymer と Web Components
Introduction to Web Components
Custom Elements defining new elements in HTML
Shadow DOM
Shadow DOM 101
Shadow DOM 201
Shadow DOM 301
HTML’s New Template Tag

この記事のシェア数

741

王
バックエンドエンジニア

LIGの王です。ウェブの全てを学ぶ為、中国は四川省より日本にやってきました。王という名に恥じぬよう、ウェブ業界のKINGとなるべく日々頑張っております。よろしくお願いいたします。