先月のGoogle I/Oで、新しいデザインガイドラインとして「Material Design」が発表されました。その中で、WebでそのMaterial Designに則ったUIを実現する手段としてPolymerが紹介されました。Polymer自体は以前よりWeb Componentsライブラリとして存在していましたが、今回それがMaterial Design向けにリデザインされたようです。
そこで、今回はこのPolymerを使って、Web Componentsがどういったものなのか学ぼうと思います。
なお、今回説明する内容は以下のGitHubリポジトリに載せていますので、そちらも合わせて参照ください。
https://github.com/bl-lia/polymer-toy

Web Componentsって何?

Web Componentsとは、ざっくり言うと「HTMLの要素を部品化する」仕組みです。
例えばヘッダやフッタ、あとはメニューなど、サイト内の各画面で共通化できるものを部品として切り出すことで、再利用性の向上や統一的な機能、レイアウトを実現し、また他の要素との関係を疎にすることが出来ます。

今までもJavaScriptなどのフレームワークで「テンプレート」と呼ばれるような、同様の仕組みは存在していましたが、例えば以下のような場合に問題が発生します。

これをブラウザで表示すると、元々body内にあった「Hello world」が「Good bye world」に変わるだけでなく、テンプレートとして挿入された要素も「Good bye template world」に書き換わってしまいます。

これを防ぐには、変えたい要素にidやclassをつけるなどして、なんとかjQueryのセレクタで絞り込めるように工夫をしたりするのですが、そうするとよく分からないidが量産されてしまったり、JavaScriptで参照するclassとCSSで参照するclassをぶつからないようにするために名前が長くなってしまったりと、とにかく煩雑で面倒でした。

そもそもこのテンプレートで実現していることは、あくまでHTMLの要素の一部を切り出してそれをJavaScriptでDOMに挿入しているだけなので、例えば意図しないCSSの影響を受けてレイアウトが崩れたり、はたまた意図しないJSのイベントに要素が反応してしまったりといったことが起こるため、HTMLの要素を完全に部品化するといったことは今までなかなか難しかったのです。

Web Componentsは、上記のような問題を解決する有効な手段となります。
以降では、このWeb Componentsを構成する4つの要素をPolymerを使って実際に動かしてみながら見ていきます。

Web Componentsを構成する4要素

Web Componentsは、以下の4つの要素で構成されています。
1. Templates
2. Imports
3. Custom Elements
4. Shadow DOM

それぞれの内容としては大まかに
1. 部品の内容を記述する仕組み
2. 1の部品を呼び出す仕組み
3. 1の部品を使う仕組み
4. 1の部品をカプセル化する仕組み
となっており、それぞれが組み合わさることでWeb Componentsが使えるようになっています。

今回は、このうち「Templates」と「Shadow DOM」にフォーカスを当てて見ていきます。

Templates

HTMLの要素をWeb Components化させる場合、要素は意味合いのある複数の要素に「部品化」されます。
この部品化された中身を記述しておくのがTemplatesの役割です。

では実際にTemplatesを作成してみましょう。
Templatesの作成には、DesignerというGUIツールがPolymerから提供されているので、それを使ってみます。
http://www.polymer-project.org/tools/designer/
このツールを使用して、Webサイトの部品として使えるようなメニューを作成してみます。

右下のPALETTEの中から”Core” > “Menu”を選択し、左側のメイン領域にドラッグ&ドロップします。

Screenshot-1TG88WcDxwBxabJ3yJCBE

ドラッグ&ドロップでただ単に貼り付けたメニューですが、この時点で既にTemplatesができあがっています。
どのようなソースコードが作成されているかは、Designerの左上の<>みたいなアイコンをクリックすることで、メイン領域の表示をコードに切り替えることができます。

Screenshot-67IHHKAvMEjPKDSHJ78b1B

先ほど貼り付けたメニューは、このようなコードで構成されています。
要素を細かく見ていくと、大きく以下のような要素で構成されていることが分かるかと思います。

  • link
  • polymer-element
  • template
  • script

ここで重要なのはpolymer-elementで、link以外のTemplatesとして部品化したい内容は全てここに含まれています。

polymer-elementにはnameという属性が設定されていますが、これに設定されている名前が、HTMLドキュメントから呼び出される際の要素名になります。
W3CのWeb Componentsのドキュメントには、Custom elementsの要素として使用する名前にはハイフンが含まれている必要があると明記されているので、ここで設定する名前もハイフンを含めたものを設定するのがベターかと思います。

polymer-elementの中には、templateとscriptが含まれています。
templateにはHTML要素やスタイルシートを、scriptにはJavaScriptを設定できますが、scriptの中にはPolymer coreというWeb ComponentsにAPI層を提供するためのJavaScriptがデフォルトで設定されています。
このPolymer coreを使うことで、例えばWeb Componentsに独自のプロパティやメソッドを設定することが出来たり、created、ready、attachedのようなあらかじめ設定されたイベントの動作を記述することが出来たりします。

また、polymer-elementにはattributesという属性を設定することが出来ます。
これは必須の属性ではありませんが、Templates内で使用する文言などを呼び出し時に動的に設定したい場合、引数として値を設定する、といったことができるようになります。
試しに、メニューのうち1つをattributesを使って設定できるようにしてみます。

attibutes属性にnew_labelを設定し、core-submenu要素のlabel属性に{{new_label}}を設定することで、Web Components呼び出し時にnew_labelに設定された値を設定できるようにしました。

ちなみに、このメニューをHTMLドキュメント側から呼び出す際は、以下のようになります。

要素名のmy-elementはTemplatesで設定したname属性に、new_label属性はTemplatesのattributes属性に設定されているnew_labelにそれぞれ対応しています。

ここまでの内容は、Githubリポジトリ内の/public/menu.htmlから見ることが出来ます。
menu.htmlは/public/elements/my-menu-element.htmlのWeb Componentsを呼び出しており、その内容が上記のものになっています。

Shadow DOM

Shadow DOMは、上記のTemplatesの内容をカプセル化する仕組みです。
もう少し丁寧に説明すると、HTML内に構成されているDOMツリーのうち、とある子ノードを別のDOM rootとして分離し、そこからシャドウツリーと呼ばれる別のDOMツリーが構成できるようになります。
絵にすると、こんな感じになります。

Shadow DOM

Polymerでは、Shadow DOMの種類として以下の3種類に分類しています。

  • Light DOM
  • Shadow DOM
  • Composed DOM

このうちLight DOMとShadow DOMについては、これによって表示がどうなるというよりも、DOMとして扱う際の振る舞いが異なります。
この2つのDOMが組み合わさっているのがComposed DOMで、これによってブラウザがどのように画面に表示するかが決まります。
実際に表示させて見てみましょう。

Githubリポジトリ内の/public/shadow-dom.htmlを開きます。

文字が表示されるだけの単純なものですが、この中に上記のLight DOMとShadow DOMの要素を含んでいます。
ソースを表示して、中身を確認してみましょう。
※ Chromeのデベロッパーツールのような開発ツールではなく、「ページのソースを表示」でソースを表示させてください。
すると、ソースはこんな感じになっていると思います。

なんかさっきの表示されている文字と微妙に違いますね。

今度は、このHTMLが参照しているWeb Componentsを見てみましょう。

あ、さっき表示されていない文字があった!

つまり、さっき画面上に描画されていた内容は、Web Componentsのテンプレート内の文言と、それを呼び出しているHTMLドキュメント側の文言が組み合わさって表示されていたのです。

今度は、ブラウザの開発ツールから、my-shadow-elementを参照してみます。
※ 今回はChromeを使って説明します
コンソール内に、以下の内容を入力します。

すると、こんな感じの結果が返ってきます。

あれ、shadow-dom.htmlの中にあった<p>要素とコメントは取得できましたが、my-shadow-element.htmlのテンプレートの内容が取得できていません。

これが、Light DOMとShadow DOMの違いです。

つまり、

  • Light DOM
    • テンプレート外の要素
    • .childNodes、.children、.innerHTMLなどの子要素操作系のプロパティから取得できる
  • Shadow DOM
    • テンプレート内の要素
    • .childNodes、.children、.innerHTMLなどの子要素操作系のプロパティから取得できない

といった感じに分けることができます。

これによってShadow DOMのカプセル化、つまり他の要素や画面のJavaScriptの影響を受けないといった事が可能になります。

まとめ

今回は、簡単ではありますがWeb Componentsがどういったものなのかというのを見てきました。

Web Componentsを使うことで、Webページ内のまとまった要素を部品化し独立したDOMとして扱うことができ、またその部品を再利用できるということがお分かりいただけたかと思います。
それだけでなく、部品は他のHTMLの要素やJavaScriptに影響を受けないため、それぞれの部品を複数人で同時に開発する、なんてこともやりやすくなるのではと考えています。

ただ、部品の描画にJavaScriptを使用しているため、闇雲に部品化を進めてしまうとページのロードが重くなってしまったり、またGooglebotのようなJavaScriptを解釈する保証のないものに対応できなくなってしまうため、その点は注意が必要です。

とはいえ、Webサイト制作において重要な要素になることは間違いないので、Web Componentsが今後さらに普及してくれればいいなと思います。