<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="http://organizeseries.com/"
	>

<channel>
	<title>基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 &#8211; HTML5Experts.jp</title>
	<atom:link href="/series/web-components-2/feed/" rel="self" type="application/rss+xml" />
	<link>https://html5experts.jp</link>
	<description>日本に、もっとエキスパートを。</description>
	<lastBuildDate>Sat, 07 Jul 2018 03:14:05 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.7.19</generator>
	<item>
		<title>進化するWeb Componentsの今、2016年最新情報</title>
		<link>/1000ch/21705/</link>
		<pubDate>Tue, 22 Nov 2016 00:00:40 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=21705</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (7)Web Componentsが変えるWeb開発の未来から、はや二年が経ちました。コミュニティでの議論やフィードバックを経...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (7)</div><p><a href="https://html5experts.jp/1000ch/8906/" data-wpel-link="internal">Web Componentsが変えるWeb開発の未来</a>から、はや二年が経ちました。コミュニティでの議論やフィードバックを経て2016年現在、Web Componentsの仕様は大きくアップデートされています。先日行われた<a href="http://gdg-tokyo.connpass.com/event/38927/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">DevFest Tokyo 2016</a>でも<a href="https://1000ch.github.io/slide/webcomponents-2016/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Components 2016 &amp; Polymer v2</a> と題してWeb Componentsの最近についてお話しました。</p>

<p>これまでGoogleを中心に策定されてきたv0の仕様を元に、新しい仕様はMozillaやAppleなどの各ブラウザベンダーの合意を改めてとりながら策定が進められています。今日はアップデートされたWeb Componentsの仕様を説明していきます。</p>

<p><img src="/wp-content/uploads/2016/11/shadow-dom-640x588.png" alt="Shadow DOM" width="640" height="588" class="alignnone size-large wp-image-21715" srcset="/wp-content/uploads/2016/11/shadow-dom.png 640w, /wp-content/uploads/2016/11/shadow-dom-300x276.png 300w, /wp-content/uploads/2016/11/shadow-dom-207x190.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>基本概念については割愛しますが、えーじさんの<a href="https://blog.agektmr.com/2014/11/shadow-dom-web-components.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Shadow DOM &#8211; Web Componentsを構成する技術:Tender Surrender</a>と<a href="https://blog.agektmr.com/2014/11/custom-elements-web-components.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Custom Elements &#8211; Web Componentsを構成する技術: Tender Surrender</a>がわかりやすいので、Shadow DOMやCustom Elementsが何なのかわからない人はまずこちらを見ましょう。</p>

<h2>Shadow DOM <strong>v1</strong>の仕様</h2>

<p>Shadow DOMはDOMにスコープをもたらす重要な仕様です。v1においては、基本的なコンセプトはそのままにAPIが見直されています。</p>

<h3>createShadowRoot()からattachShadow()へ</h3>

<p><code>createShadowRoot()</code> で行っていたShadow Rootの作成ですが、v1では <code>attachShadow()</code> を使います。関数がShadow Rootを返す点はこれまで同様です。</p>

<p></p><pre class="crayon-plain-tag">const div = document.querySelector('div');
const shadowRoot = div.attachShadow({
  mode: 'open' // or 'close'
});

// readonly な shadowRoot プロパティの追加
console.log(div.shadowRoot);</pre><p></p>

<p><code>attachShadow()</code> は引数にオプションを取ります。v1においてShadow DOMにはOpenedなモードとClosedなモードが存在し、<code>mode: 'open'</code> または <code>mode: 'close'</code> のように指定します。</p>

<p>Shadow DOMをホストできるHTML要素に <code>shadowRoot</code> という読み取り専用のプロパティが追加されており、要素のShadow DOMはこれを通して参照します。要素が Shadow DOMをホストした時点で <code>shadowRoot</code> プロパティは参照を返すようになり、Shadow DOMがないうちは <code>null</code> です。DevToolsで <code>document.querySelector('div').shadowRoot</code>を実行すると <code>undefined</code> ではなく <code>null</code> が（きっと）返ってきます。</p>

<h3>OpenedなShadow DOMとClosedなShadow DOM</h3>

<p>Shadow DOMのモードにOpenedとClosedがあることを先に述べましたが、これはShadow DOMの外の世界からアクセスできるかどうかを示します。このため、外部からアクセス不能なClosedなShadow DOMをホストする要素の <code>shadowRoot</code> プロパティは、<code>null</code> を返します。</p>

<p>外部からのアクセスを許すべきか封じるべきかは賛否両論があったため、この<strong>モード</strong>という形で合意形成されました。</p>

<p>ClosedなShadow DOMをホストする要素は第三者に編集されないので、コンポーネントを作ったときに独立性を保つことができます。ネイティブの <code>video</code> 要素を Chrome で閲覧すると、各種制御UIがShadow DOMで実装されていることがわかりますが、このように外部から操作されたくない場合にはClosedモードが適しているのでしょう。逆にOpenedでアクセスの余地を残せば、より柔軟にコンポーネントを利用できるはずです。</p>

<h3>複数Shadow Rootの廃止</h3>

<p>v0では単一の要素に対してShadow Rootを複数持つことが許可されていましたが、v1では禁止されます。そのため <code>attachShadow()</code> を2回以上実行すると例外が発生します。</p>

<h3>Shadow DOMをホストできる要素が限定的に</h3>

<p>v0ではあらゆる要素がShadow DOMをホストすることが可能でしたが、v1では限定的になります。許可が予定されているのは次の要素です。</p>

<p></p><pre class="crayon-plain-tag">article, aside, blockquote, body, div, footer, h1, h2, h3, h4, h5, h6, header, 
nav, p, section, span</pre><p></p>

<p>これらのネイティブ要素に加えて、カスタム要素もShadow DOMをホストできます。<code>input</code>や <code>img</code>などの置換要素がShadow DOMをホストできなくなるようです。</p>

<h3>Insertion PointsからSlotsへ</h3>

<p>カスタム要素で囲むコンテンツがどのように表示されるかは、カスタム要素に含まれる <code>content</code> 要素によって挿入先（Insertion Points）が決まっていました。v1では <code>slot</code> 要素によるSlotsに変わります。</p>

<p><code>form-container</code> というカスタム要素を例に解説します。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- v0での&lt;content&gt;によるInsertion Pointsの決定 --&gt;
&lt;template&gt;
  &lt;style&gt;
    ::content input {
      background: skyblue;
    }
  &lt;/style&gt;
  &lt;div&gt;
    &lt;content select=".class-name"&gt;&lt;/content&gt;
    &lt;content&gt;&lt;/content&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;form-container&gt;
  &lt;input class="class-name" type="text"&gt;
  &lt;button&gt;Button&lt;/button&gt;
&lt;/form-container&gt;</pre><p></p>

<p><code>form-container</code> で囲まれた <code>input</code> と <code>button</code> は、それぞれ <code>form-container</code> の <code>content</code> に挿入されます。挿入先は <code>select</code> 属性に指定するセレクタで決定されていました。</p>

<p>v1からは次のように <code>slot</code> に置き換わります。<code>slot</code> は <code>name</code> 属性で命名可能で、カスタム要素を利用する側から能動的に指定できるようになります。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- v1での&lt;slot&gt;によるSlotsの決定 --&gt;
&lt;template&gt;
  &lt;style&gt;
    ::slotted(input) {
      background: skyblue;
    }
  &lt;/style&gt;
  &lt;div&gt;
    &lt;slot name="slot-name"&gt;&lt;/slot&gt;
    &lt;slot&gt;&lt;/slot&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;form-container&gt;
  &lt;input slot="slot-name" type="text"&gt;
  &lt;button&gt;Button&lt;/button&gt;
&lt;/form-container&gt;</pre><p></p>

<p>に挿入された要素を参照する疑似セレクタも <code>::content</code> の子孫から、 <code>::slotted()</code> になっています。</p>

<h3>さらなる詳細について</h3>

<p>Shadow DOMのスペックエディタであるGoogleの夷藤さんの<a href="http://hayato.io/2016/shadowdomv1/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">What&#8217;s New in Shadow DOM v1 (by examples)</a>という記事を見ましょう。スペックに合わせて記事も随時アップデートされています。</p>

<h2>Custom Elements v1の仕様</h2>

<p>Custom Elementsは開発者が任意の要素を再定義可能にする、Shadow DOM同様に重要な機能です。</p>

<h3>ES2015 classベースの要素定義とライフサイクルコールバック</h3>

<p>Custom Elements v1ではES2015の <code>class</code> 記法を使った定義が推奨されます。もちろん <code>function</code> を使っても同等の実装は可能ですが、ブラウザのサポートも進みつつある <code>class</code> で書いていくのが望ましいでしょう。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- v0でのカスタム要素の定義 --&gt;
&lt;script&gt;
  const FooElement = Object.create(HTMLElement.prototype);
  FooElement.createdCallback = () =&gt; { ... };
  FooElement.attachedCallback = () =&gt; { ... };
  FooElement.detachedCallback = () =&gt; { ... };
  FooElement.attributeChangedCallback = () =&gt; { ... };

  document.registerElement('foo-element', {
    prototype: FooElement
  });
&lt;/script&gt;</pre><p></p>

<p>ライフサイクルコールバックも <code>class</code> の <code>constructor()</code> を活かしリネームされているほか、<code>adoptedCallback()</code> が追加されています。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- v1で推奨されるclassを使ったカスタム要素の定義 --&gt;
&lt;script&gt;
  class FooElement extends HTMLElement {
    constructor() { ... }
    connectedCallback() { ... }
    disconnectedCallback() { ... }
    attributeChangedCallback() { ... }
    adoptedCallback() { ... }
  }

  window.customElements.define('foo-element', FooElement);
&lt;/script&gt;</pre><p></p>

<p><code>adoptedCallback(oldDocument, newDocument)</code> はオーナーとなるドキュメントが変わったタイミングのハンドラです。同一のカスタム要素名が追加先のコンテキストで既に登録されている場合に、フォールバックするなどのユースケースがあるようです。</p>

<h3>window.customElements へ</h3>

<p>先に出ていますが、カスタム要素を定義する関数も <code>document.registerElement</code> から <code>customElements.define()</code> に変更されています。 <code>customElements</code> はブラウザコンテキストに追加される新たなグローバルオブジェクトです。</p>

<p><code>customElements.define()</code> の他には次のような関数が提案されています。</p>

<p></p><pre class="crayon-plain-tag">// &lt;foo-element&gt;コンストラクタを参照する
const FooElement = customElements.get('foo-element');

// &lt;foo-element&gt;が定義されたタイミングを取得する
customElements.whenDefined('foo-element').then(() =&gt; {
  console.log('foo-element is defined');
});</pre><p></p>

<p>カスタム要素は非同期で処理されるため、HTMLドキュメントに存在するカスタム要素の振る舞いは定義されるまでブラウザは知り得ません。そのため、ロードの遅延などと相まって意図しないガタツキなどを招きます。そのカスタム要素が他の要素をSlotsに追加するようなものであれば、影響はなおさら大きくなります。</p>

<p>それを防ぐには対象のカスタム要素が定義されるタイミングを知る必要があります。<code>customElements.whenDefined()</code> は指定のカスタム要素が定義されるタイミングをPromiseで知らせてくれます。これによって、初期表示ではカスタム要素を非表示にしておいて、ブラウザが評価可能になるタイミングで<code>display: none;</code> を外すといった対応も可能になるでしょう。</p>

<h3>Type Extensionの行方</h3>

<p><code>button is="foo-button"</code> のように、元の振る舞いを活かしつつ機能を拡張するType Extensionという機能がv0では検討されていました。この機能については<a href="https://github.com/w3c/webcomponents/issues/509" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">今なお議論が続けられています</a>。複雑な機能を持つ既存要素の振る舞いを壊さないような機能拡張の難しさへの懸念から反対意見も出ており、WebKitは実装を見送っています。行方が気になる人は引き続きウォッチしてみてください。</p>

<h2>HTML Imports</h2>

<p>HTML Importsについては<a href="https://hacks.mozilla.org/2014/12/mozilla-and-web-components/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Mozillaが2014年に実装を見送っており</a>、昨年（2015年）においても<a href="https://hacks.mozilla.org/2015/06/the-state-of-web-components/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">引き続きES6 Modulesによるリソース解決が適切に動作するかどうかを見守る</a>姿勢を見せています。</p>

<h2>ブラウザサポート状況</h2>

<p>Shadow DOM v1とCustom Elements v1はChromiumにて先行実装されており、Shadow DOM v1はChrome 53とOpera 40から、Custom Elements v1はChrome 54とOpera 41から利用可能です。</p>

<p>またWebKitでも先行して進められており、Shadow DOM v1がSafari 10に実装されている他、Custom Elements v1もSafari Technology Preview 14に実装されており、メニューの<strong>Develop</strong> → <strong>Experimental Features</strong> → <strong>Custom Elements</strong>から有効化できます。</p>

<ul>
<li><a href="https://developer.apple.com/library/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_0.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Safari 10.0</a></li>
<li><a href="https://developer.apple.com/safari/technology-preview/release-notes/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Safari Technology Preview Release Notes</a></li>
</ul>

<p>これらが意味するところは大きく、モバイルプラットフォームで大きなシェアを占めるSafariでサポートされることで、モバイルをターゲットとしているプロダクトではWeb Componentsの利用がかなり現実的になってきたと言えます。</p>

<p>デスクトップブラウザについてはFirefoxとEdgeで実装が進むを期待したいところです。EdgeはDeveloper Feedbackで<a href="https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/6261298-custom-elements" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Custom Elements</a>と<a href="https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/6261298-custom-elements" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Shadow DOM</a>に投票しておくと、早く実装してくれるかもしれません <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>Polymer v1.1のAPIまとめと周辺リソースの紹介</title>
		<link>/1000ch/17410/</link>
		<pubDate>Thu, 29 Oct 2015 01:06:17 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Polymer]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=17410</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (6)Googleが開発するWeb ComponentsのライブラリPolymerのバージョン1.1が、2015年8月13日に...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (6)</div><p>Googleが開発するWeb Componentsのライブラリ<a href="https://www.polymer-project.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer</a>のバージョン1.1が、2015年8月13日にリリースされました。本記事では、Polymer v1.1のAPIの主要なAPIを解説しつつ、その参考情報を紹介していきます。</p>

<p>また、この記事は「<a href="https://html5experts.jp/1000ch/14400/" data-wpel-link="internal">Web ComponentsのこれからーPolymer 0.8、X-Tag、Brick、Bosonic</a>」を事前に読むと理解が深まりますが、これからPolymer v1.1を始めてみるということであれば、本記事単体でも参考にしてもらえればと思います。</p>

<h1>Polymer v1.1までの変更点</h1>

<p>v0.5からv0.8にかけての差分は<a href="https://html5experts.jp/1000ch/14400/" data-wpel-link="internal">前回の記事</a>にて紹介しましたが、その1週間後にv0.9がリリースされ、さらに2週間後にv1.0がリリースされています。</p>

<ul>
<li><a href="https://blog.polymer-project.org/updates/2015/03/27/why-0.8/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">0.8 released! &#8211; polymer blog</a></li>
<li><a href="https://blog.polymer-project.org/announcements/2015/05/14/0.9-release/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">0.9 released! &#8211; polymer blog</a></li>
<li><a href="https://blog.polymer-project.org/announcements/2015/05/29/one-dot-oh/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">1.0 &#8211; polymer blog</a></li>
<li><a href="https://blog.polymer-project.org/announcements/2015/08/13/1.1-release/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">1.1 Release &#8211; polymer blog</a></li>
<li><a href="https://www.polymer-project.org/1.0/docs/migration.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Migration guide</a></li>
</ul>

<p>v0.8からv1.0にかけてAPIに変更が加えられていますが、v1.0からv1.1へかけては破壊的なAPIの変更はありません。今回は以降で、Polymer v1.x系の主な機能と、参考リソースの紹介をしていきます。</p>

<p><img src="/wp-content/uploads/2015/10/polymer-project-640x541.png" alt="polymer-project" width="640" height="541" class="alignnone size-large wp-image-17419" srcset="/wp-content/uploads/2015/10/polymer-project.png 640w, /wp-content/uploads/2015/10/polymer-project-300x254.png 300w, /wp-content/uploads/2015/10/polymer-project-207x175.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>カスタム要素の登録</h2>

<p>&lt;dom-module&gt; に &lt;template&gt; と &lt;script&gt; を記述して宣言します。 &lt;script&gt; 内で実行している<code>Polymer()</code>関数は、 &lt;dom-module&gt; に指定しているID（カスタム要素の名前）を渡し、その他の引数でカスタム要素の振る舞いを決定します。返り値にはカスタム要素のコンストラクタが返却されるので、それを使ってカスタム要素のインスタンスを生成することも可能です。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel="import" href="bower_components/polymer/polymer.html"&gt;

&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;style&gt;
      div { color: red; }
    &lt;/style&gt;
    &lt;div&gt;Shadow DOM in My Element&lt;/div&gt;
  &lt;/template&gt;

  &lt;script&gt;
    var MyElement = Polymer({is: 'my-element'});
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<p>v0.8との違いは、カプセル化されるカスタム要素のスタイルを記述する &lt;style&gt; と、<code>Polymer</code>関数を実行する &lt;script&gt; を &lt;dom-module&gt; の中に書くようになった点です。v0.5までの &lt;polymer-element&gt; を彷彿させます。v1.1では &lt;template&gt; の外側に &lt;style&gt; を書いてもスタイルが適用されますが、パフォーマンスは悪く推奨されません。</p>

<h3>カスタムコンストラクタ</h3>

<p>カスタム要素のコンストラクタを自前で書きたい場合は、<code>Polymer()</code>関数の引数として、<code>factoryImpl</code>キーワードと共にコンストラクタ関数を渡します。カスタムコンストラクタによって、引数を渡すことが可能になります。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  var MyElement = Polymer({
    is: 'my-element',
    factoryImpl: function(foo, bar) {
      // custom constructor is called
    }
  });

  var myInstance = new MyElement('foo', 100);
&lt;/script&gt;</pre><p></p>

<p>この<code>factoryImpl</code>はコンストラクタ関数（ここでは<code>MyElement</code>）を<code>new</code>キーワードと共呼び出した場合のみ実行され、HTMLドキュメントで &lt;my-element&gt; が評価された場合には呼ばれません。</p>

<h3>ネイティブ要素の継承</h3>

<p>ネイティブ要素を継承したカスタム要素を作る場合は、<code>Polymer()</code>関数の引数に<code>extends</code>キーワードに継承するネイティブのHTML要素を指定します。v0.5まではカスタム要素の継承もサポートされていましたが、Custom Elementsの仕様として<code>document.registerElement()</code>にカスタム要素名を渡せないのと同様、ブラウザネイティブの要素（<code>input</code>や<code>button</code>など）のみを指定できるようになっています。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  var MyElement = Polymer({
    is: 'my-element',
    extends: 'button'
  });
&lt;/script&gt;</pre><p></p>

<p>ネイティブ要素を拡張するカスタム要素を使うには、<code>extends</code>に指定した要素にカスタム要素名を<code>is</code>属性に指定します。JavaScriptからインスタンスを生成する場合は、コンストラクタを<code>new</code>するか、<code>document.registerElement()</code>の第二引数にカスタム要素名を指定してください。</p>

<p></p><pre class="crayon-plain-tag">&lt;button is="my-element"&gt;My Element&lt;/button&gt;

&lt;script&gt;
  console.log(new MyElement() instanceof HTMLButtonElement);
  // =&gt; true
  console.log(document.createElement('button', 'my-element') instanceof HTMLButtonElement);
  // =&gt; true
&lt;/script&gt;</pre><p></p>

<h3>ライフサイクルコールバック</h3>

<p>カスタム要素には4つのライフサイクルが存在し、Polymerでもそれらをハンドリングできます。ライフサイクルについては<a href="https://html5experts.jp/1000ch/11142/" data-wpel-link="internal">Web Componentsを構成する4つの仕様 ー Web Components基礎編</a>という記事の「カスタム要素の挙動を設定する」をセクションにて解説しています。ブラウザネイティブでは<code>createdCallback</code>・<code>attachedCallback</code>・<code>detachedCallback</code>、<code>attributeChangedCallback</code>の4つが定義されていますが、Polymerでは<code>Callback</code>が省略されている他、<code>ready</code>というShadow Root配下のDOM構築が完了したタイミングで発火されるハンドラも定義できるようになっています。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  Polymer({
    is: 'my-element',
    created: function () {
      console.log('my-elementが生成されました');
    },
    attached: function () {
      console.log('my-elementがHTMLドキュメントに追加されました');
    },
    detached: function () {
      console.log('my-elementがHTMLドキュメントから切り離されました');
    },
    attributeChanged: function (name, type) {
      console.log('my-elementの' + name + '属性が変更されました');
    },
    ready: function () {
      console.log('my-elementのDOM構築が完了しました');
    }
  });
&lt;/script&gt;</pre><p></p>

<p>ハンドラの実行順序をまとめると以下のようになります。</p>

<ol>
<li><code>created</code>コールバック</li>
<li><code>ready</code>コールバック</li>
<li><code>factoryImpl</code>コールバック</li>
<li><code>attached</code>コールバック</li>
<li>(<code>detached</code>コールバック)</li>
</ol>

<h3><code>behaviors</code>を使った振る舞いの制御</h3>

<p>カスタム要素の振る舞いを制御するには、これまでのように<code>Polymer()</code>に直接ハンドラなどを指定するほか、<code>behaviors</code>にプロトタイプとなるようなオブジェクトを渡すことでも可能です。オブジェクトで定義できるのは先程登場したライフサイクルコールバック、後述するデフォルト属性やプロパティ、オブザーバー（<code>observer</code>）、イベントハンドラの登録（<code>listener</code>）です。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
var MyBehavior = {
  ready: function() {
    console.log('DOM is ready');
  }
};

Polymer({
  is: 'my-element',
  behaviors: [MyBehavior]
});
&lt;/script&gt;</pre><p></p>

<p>見ての通り、<code>behaviors</code>にはオブジェクトを配列で指定することが可能です。複数指定されてハンドラが重複する（例えば、複数のオブジェクトで<code>ready</code>が定義されている）場合は、配列上で後から出現するオブジェクトが優先されます（つまり右辺が優先されます）。</p>

<h2>カスタム要素のデフォルト属性</h2>

<p>カスタム要素のデフォルト属性は、引数の<code>hostAttributes</code>に定義します。<code>hostAttributes</code>に指定した属性とその値は、カスタム要素の属性のデフォルト値になります。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  var MyElement = Polymer({
    is: 'my-element',
    hostAttributes: {
      role: 'navigation'
    }
  });
&lt;/script&gt;</pre><p></p>

<p>この時 &lt;my-element&gt; は、デフォルトで &lt;my-element role=&quot;navigation&quot;&gt;&lt;/my-element&gt; として評価されます。</p>

<h2>カスタム要素のプロパティ</h2>

<p>カスタム要素のプロパティは、引数の<code>properties</code>に定義します。プロパティ名をキーに、<code>type</code>や<code>value</code>などの属性を渡すことでプロパティの特徴を定義します。簡略化された形としては、<code>propertyName: type</code>という指定も可能です</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  Polymer({
    is: 'my-element',
    properties: {
      foo: {
        type: Number,
        value: 0,
        observer: 'fooChanged'
      }
      bar: {
        type: String,
        computed: 'computeBar(foo)'
      }
    }
    fooChanged: function(newValue, oldValue) {
      // ...
    },
    computeBar: function(foo) {
      return String(foo + 100);
    }
  });
&lt;/script&gt;</pre><p></p>

<p>ここでは<code>foo</code>と<code>bar</code>の2つを<code>my-element</code>のプロパティとして定義していますが、それぞれ<code>observer</code>と<code>computed</code>という属性を渡しています。</p>

<h3>オブザーバー関数を指定する<code>observer</code></h3>

<p><code>observer</code>はプロパティの変更を監視するオブザーバー関数を指定する属性です。ここでは<code>fooChanged</code>という関数を定義しているので、関数名を文字列で渡しています。v0.5までは<code>propertyNameChanged</code>といったような命名規則を用いてオブザーバー関数を定義していましたが、v1.1においてその機能はありません。監視する関数には引数として、変更後の値（<code>newValue</code>）と変更前の値（<code>oldValue</code>）が引数と渡されるので、前後値を利用することができます。</p>

<h3>コンピュート関数を指定する<code>computed</code></h3>

<p><code>computed</code>はプロパティの値を動的に算出するための関数を指定する属性です。<code>observer</code>同様に関数名を文字列で渡しますが、ここでは<code>bar</code>プロパティに対し<code>computeBar(foo)</code>という値を渡しています。この記述によって<code>computeBar</code>関数には<code>foo</code>プロパティが引数として渡され、<code>bar</code>の値は関数が返却する値を参照します。</p>

<h3>HTML属性に反映するかどうかを指定する<code>reflectToAttribute</code></h3>

<p>プロパティの値に変更があっても通常はHTML属性に反映されませんが、<code>reflectToAttribute</code>に<code>true</code>を指定するとHTMLの属性も更新されるようになります。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  Polymer({
    is: 'my-element',
    properties: {
      foo: {
        type: String,
        reflectToAttribute: true
      }
    },
    ready: function() {
      this.foo = 'Hello!';
      // this.setAttribute('foo', 'Hello!');と同等の振る舞いが得られる
    }
  });
&lt;/script&gt;</pre><p></p>

<p>この時、<code>ready</code>の関数が実行されたタイミングで &lt;my-element&gt;&lt;/my-element&gt; は &lt;my-element foo=&quot;Hello!&quot;&gt;&lt;/my-element&gt; となり、<code>setAttribute('foo', 'Hello!')</code>を実行したときと同様の振る舞いが得られます。</p>

<h2>イベントハンドラの登録</h2>

<p>v0.5以前まではイベントハンドラの登録をブラケットを使って宣言的に定義していましたが、v1.1からはブラケットを使わなくなった他、<code>listeners</code>プロパティにイベントとそのハンドラをハッシュ形式で定義が可能です。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;button on-click="clickHandler"&gt;Button&lt;/button&gt;
  &lt;/template&gt;

  &lt;script&gt;
    var MyElement = Polymer({
      is: 'my-element',
      listeners: {
        'tap': 'myElementTapHandler'
      },
      clickHandler: function(e, detail) {
        // button click handler
      },
      myElementTapHandler: function(e, detail) {
        // my-element tap handler
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<h3>ジェスチャーイベントのハンドリング</h3>

<p>先程のサンプルで<code>tap</code>というDOMネイティブには存在しないイベントに対して<code>myElementTapHandler</code>というハンドラを登録していますが、Polymerは自前で実装するにはやや面倒な4つのイベントをサポートしています。</p>

<ul>
<li><code>down</code>: マウスや指によって要素が押下された時</li>
<li><code>up</code>: マウスや指が押下後に離された時</li>
<li><code>tap</code>: <code>down</code>と<code>up</code>が連続して発生した時</li>
<li><code>track</code>: マウスや指が押下されたまま移動した時</li>
</ul>

<p>それぞれのイベントのハンドラに渡されるイベント引数の<code>detail</code>プロパティには様々な付加情報が渡されます。詳しくは<a href="https://www.polymer-project.org/1.0/docs/devguide/events.html#gestures" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">公式ドキュメント</a>を確認してください。</p>

<h3>カスタムイベントの発火</h3>

<p><code>fire</code>という関数を実行することで、ホスト要素を呼び出し元オブジェクトとしてカスタムイベントを発行することが出来ます。引数にはカスタムイベント名と、ハンドラの<code>detail</code>プロパティに渡すデータを指定します。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;button on-click="clickHandler"&gt;Button&lt;/button&gt;
  &lt;/template&gt;

  &lt;script&gt;
    var MyElement = Polymer({
      is: 'my-element',
      clickHandler: function(e, detail) {
        this.fire('foo', {
          bar: 100
        });
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;

&lt;my-element&gt;&lt;/my-element&gt;

&lt;script&gt;
  var myElement = document.querySelector('my-element');
  myElement.addEventListener('foo', function(e) {
    console.log(e.detail.bar);
    // =&gt; 100
  });
&lt;/script&gt;</pre><p></p>

<p>ここでは &lt;my-element&gt; のボタンをクリックした時に<code>foo</code>というカスタムイベントを発行し、付加データとして<code>{ bar: 100 }</code>という値を渡しています。</p>

<h2>データバインディング</h2>

<p>データバインディングは以前と同様にカーリーブラケット<code>{{}}</code>およびスクエアブラケット<code>[[]]</code>で指定します。ブラケットではプロパティおよびコンピュート関数を評価することが可能で、v0.5まで有効だった演算子などはサポートされていません。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;input value="{{foo}}" type="text"&gt;
    &lt;input value="[[bar]]" type="text"&gt;
  &lt;/template&gt;

  &lt;script&gt;
    Polymer({
      is: 'my-element',
      properties: {
        foo: String,
        bar: Number
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<p>ここでは&lt;my-element&gt;のプロパティとして定義している<code>foo</code>や<code>bar</code>を、 &lt;input&gt; の<code>value</code>にバインディングしています。これによって<code>foo</code>や<code>bar</code>の値に変更があると、&lt;input&gt;に自動で反映されます。</p>

<h3>カーリーブラケット<code>{{}}</code>とスクエアブラケット<code>[[]]</code></h3>

<p>カーリーブラケット<code>{{}}</code>とスクエアブラケット<code>[[]]</code>の差は、データバインディングの方向が一方向に制限されるかどうかの違いです。<code>{{}}</code>のデータバインディングは双方向か一方向かが記述によって変わりますが、<code>[[]]</code>は一方向のみであり参照しているプロパティを受け取るのみです。</p>

<p>カーリーブラケット<code>{{}}</code>で指定した際にバインディングの振る舞いを決定するのは、プロパティの<code>notify</code>フラグと<code>readOnly</code>フラグです。</p>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  Polymer({
    is: 'my-element',
    properties: {
      foo: {
        type: String,
        notify: true
      },
      bar: {
        type: Number,
        readOnly: true
      }
    }
  });
&lt;/script&gt;</pre><p></p>

<p><code>notify</code>フラグはプロパティの変更をホスト要素に通知するかどうか、<code>readOnly</code>フラグはプロパティが読み取り専用かどうかを決定します。この例では<code>notify: true</code>なので、 &lt;my-element foo=&quot;{{value}}&quot;&gt;&lt;/my-element&gt; としたときに<code>foo</code>に変更があると<code>value</code>へ値が反映されます。</p>

<h3>ネイティブ要素との双方向データバインディング</h3>

<p>ネイティブ要素との双方向データバインディングも <strong>target-prop=&quot;{{hostProp::target-change-event}}&quot;</strong> という構文でサポートしています。ここでは&lt;input&gt;に入力された値に括弧を付与して&lt;div&gt;に埋め込んでいます。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;input value="{{foo::input}}" type="text"&gt;
    &lt;div&gt;{{computeBar(foo)}}&lt;/div&gt;
  &lt;/template&gt;

  &lt;script&gt;
    Polymer({
      is: 'my-element',
      properties: {
        foo: {
          type: String
        }
      },
      computeBar: function(foo) {
        return `「${foo}」`;
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<p>入力された値を参照するタイミングは<code>input</code>イベントで、バインドするホストプロパティは<code>foo</code>なので、 <strong>input value=&quot;{{foo::input}}&quot;&gt;</strong> になります。これによって、ネイティブ要素のプロパティからカスタム要素へのプロパティへのデータバインディングが行われます。</p>

<h3>属性とのデータバインディング</h3>

<p>属性に対してデータをバインディングするには以下のように<code>$=</code>を使い、ブラケット<code>{{}}</code>にはこれまでと同様にカスタム要素のプロパティやコンピュート関数を記述します。</p>

<p></p><pre class="crayon-plain-tag">&lt;template&gt;
  
  &lt;!-- Attribute binding --&gt;
  &lt;my-element selected$="{{value}}"&gt;&lt;/my-element&gt;
  &lt;!-- results in &lt;my-element&gt;.setAttribute('selected', this.value); --&gt;

  &lt;!-- Property binding --&gt;
  &lt;my-element selected="{{value}}"&gt;&lt;/my-element&gt;
  &lt;!-- results in &lt;my-element&gt;.selected = this.value; --&gt;
  
&lt;/template&gt;</pre><p></p>

<p>プロパティとのデータバインディングには &lt;my-element foo={{value}}&gt; と記述することで<code>document.querySelector('my-element').foo = value</code>を振る舞いますが、HTMLの属性にバインディングするためには &lt;my-element foo$={{value}}&gt; のように<code>$=</code>で指定することで <code>document.querySelector('my-element').setAttribute('foo', value)</code> が動的に実行されます。</p>

<p>ネイティブの属性である<code>class</code>や<code>style</code>には、次のように<code>$=</code>でバインディングすることが可能です。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- class --&gt;
&lt;div class$="{{foo}}"&gt;&lt;/div&gt;

&lt;!-- style --&gt;
&lt;div style$="{{background}}"&gt;&lt;/div&gt;

&lt;!-- href --&gt;
&lt;a href$="{{url}}"&gt;

&lt;!-- label for --&gt;
&lt;label for$="{{bar}}"&gt;&lt;/label&gt;

&lt;!-- dataset --&gt;
&lt;div data-bar$="{{baz}}"&gt;&lt;/div&gt;</pre><p></p>

<p>HTML上の属性名とDOMのプロパティ名が異なる場合には留意が必要でしょう。data-*の場合、<code>data-foo</code>への実際のプロパティは<code>element.dataset.foo</code>となるため、<code>data-foo={{bar}}</code>ではバインディングできず、<code>element.setAttribute('data-foo', bar)</code>となる<code>data-foo$={{bar}}</code>を使う必要があります。</p>

<h3>Templateのリピート</h3>

<p>配列のデータは &lt;template is=&quot;dom-repeat&quot;&gt; を使ってリピートし、要素ひとつひとつを参照することが出来ます。以下は<code>is="dom-repeat"</code>を使って配列を展開しているサンプルです（公式より引用）。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="employee-list"&gt;

  &lt;template&gt;
    &lt;div&gt; Employee list: &lt;/div&gt;
    &lt;template is="dom-repeat" items="{{employees}}"&gt;
      &lt;div&gt;# &lt;span&gt;{{index}}&lt;/span&gt;&lt;/div&gt;
      &lt;div&gt;First name: &lt;span&gt;{{item.first}}&lt;/span&gt;&lt;/div&gt;
      &lt;div&gt;Last name: &lt;span&gt;{{item.last}}&lt;/span&gt;&lt;/div&gt;
    &lt;/template&gt;
  &lt;/template&gt;

  &lt;script&gt;
    Polymer({
      is: 'employee-list',
      ready: function() {
        this.employees = [
          {first: 'Bob', last: 'Smith'},
          {first: 'Sally', last: 'Johnson'}
        ];
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<p>&lt;template is=&quot;dom-repeat&quot;&gt; に加えて展開したい配列を<code>items</code>属性で指定します。配列の要素の数だけ &lt;template &gt; の内部が繰り返されますが、この中では要素を表す<code>item</code>と配列のインデックスを示す<code>index</code>という二つの変数を参照できます。</p>

<p><code>items</code>で参照している配列に変更を加える場合には、インスタンスの関数をそのまま使うのではなくPolymerのインスタンスが備えている専用の関数を使います。例えばこのサンプルの<code>employees</code>に値を追加する場合は、 <code>this.employees.push({...})</code>ではなく<code>this.push('employees', {...})</code>とする必要があります。<code>push</code>の他には<code>pop</code>、<code>splice</code>、<code>shift</code>、<code>unshift</code>も同様です。これらの関数を使わないと、配列への変更が検知されずに再描画されません。</p>

<p>Polymerのデータバインディングの更なる詳細については<a href="https://www.polymer-project.org/1.0/docs/devguide/data-binding.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Data binding &#8211; Polymer 1.0</a>を参照してください。</p>

<h2>Polymer内部で行うDOM操作</h2>

<p>&lt;dom-module&gt;内部のDOMの操作を行う場合は、Shadow RootのDOM APIではなく専用のインターフェースが用意されています。</p>

<h3>IDが与えられている要素の参照</h3>

<p>要素に対してIDが与えられていると、<code>this.$.id</code>という形で自動的に参照が生成されます。これはv0.5まで存在していた機能なので、馴染みのある人もいるかと思います。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    This is my-element &lt;span id="foo"&gt;&lt;/span&gt;!
  &lt;/template&gt;

  &lt;script&gt;
    Polymer({
      is: 'my-element',
      ready: function() {
        this.$.foo.textContent = this.foo;
      }
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<p>また、動的に生成された要素を参照する場合は<code>this.$$(selector)</code>という関数が用意されています。これにCSSセレクタを渡すと、セレクタにマッチする最初の要素が返却されます。</p>

<h3><code>Polymer.dom()</code>を使ったDOM操作</h3>

<p>Polymer内部のDOM操作を行う場合は、Shadow Root配下の要素のDOM APIを直接実行するのではなく、<code>Polymer.dom()</code>という専用のAPIが用意されています。<code>Polymer.dom()</code>の引数にはNodeを渡し、返却されるオブジェクトには、ネイティブのDOM APIと同等の振る舞いをするAPIが用意されています。いずれもDOMのAPIと同じ命名と引数で設計されていますが、サブセットとして用意されているに過ぎず、完全に互換性があるわけではないことに注意してください。</p>

<h4>親子関係</h4>

<ul>
<li><code>Polymer.dom(parent).childNodes</code></li>
<li><code>Polymer.dom(node).parentNode</code></li>
<li><code>Polymer.dom(node).firstChild</code></li>
<li><code>Polymer.dom(node).lastChild</code></li>
<li><code>Polymer.dom(node).firstElementChild</code></li>
<li><code>Polymer.dom(node).lastElementChild</code></li>
<li><code>Polymer.dom(node).previousSibling</code></li>
<li><code>Polymer.dom(node).nextSibling</code></li>
<li><code>Polymer.dom(node).textContent</code></li>
<li><code>Polymer.dom(node).innerHTML</code></li>
</ul>

<h4>クエリ</h4>

<ul>
<li><code>Polymer.dom(parent).querySelector(selector)</code></li>
<li><code>Polymer.dom(parent).querySelectorAll(selector)</code></li>
</ul>

<h4>コンテンツの参照</h4>

<ul>
<li><code>Polymer.dom(contentElement).getDistributedNodes()</code></li>
<li><code>Polymer.dom(node).getDestinationInsertionPoints()</code></li>
</ul>

<h4>属性の操作</h4>

<ul>
<li><code>Polymer.dom(node).setAttribute(attribute, value)</code></li>
<li><code>Polymer.dom(node).removeAttribute(attribute)</code></li>
<li><code>Polymer.dom(node).classList</code></li>
</ul>

<p><code>Polymer.dom</code>から行う追加や削除といったDOM操作は、パフォーマンスを考慮し遅延して実行するようになっています。そのため、操作後のノードの座標やgetComputedStyle()を使ったスタイルの取得をする場合は、操作を<code>Polymer.dom.flush()</code>を使って適時実行し、反映します。</p>

<h2>カスタム要素のスタイリング</h2>

<p>CSSのスタイリングは、これまでと同様にShadow DOMをスタイリングする記法が適用されます。PolymerはShadow DOMの実装状況で振るまいが変わらないように、Shady DOMという軽量なポリフィルを採用しているため、CSSセレクタをネイティブと同様に書くことはできません。</p>

<p>例えば、&lt;content&gt;を参照する<code>::content</code>セレクタです。Shadow DOMがブラウザネイティブに実装されていれば単一でも評価されるはずですが、Shady DOMの互換性として<code>::content</code>セレクタには何らかの親セレクタを指定する必要があります。これは <strong>::content .bar</strong> と記述された場合にShady DOMで実現しているスコープを再現できなくなるためです。この場合、 <strong>.foo &gt; ::content .bar</strong> と書く必要があり、実際には <strong>.foo &gt; .bar</strong> のように評価されます。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;

  &lt;template&gt;
    &lt;style&gt;
      :host {
        display: block;
        border: 1px solid red;
      }
      .foo &gt; ::content .bar {
        background: orange;
      }
    &lt;/style&gt;

    &lt;div class="foo"&gt;&lt;content&gt;&lt;/content&gt;&lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
    Polymer({
      is: 'my-element'
    });
  &lt;/script&gt;

&lt;/dom-module&gt;</pre><p></p>

<h2>まとめ</h2>

<p>Polymer v1.1の主な機能について解説しました。v0.5 → v0.8 → v0.9 → v1.0&#8230;とAPIに多くの変更が加えられてきましたが、メジャーリリースになったことで、これまでのような大きな変更は（ <strong>おそらく</strong> ）なくなるでしょう。</p>

<p>最後に、今年9月の14日〜15日にオランダのアムステルダムにて開催された<a href="https://www.polymer-project.org/summit" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer Summit 2015</a>にて、参考リソースが多く公開されたのでそれを紹介します。</p>

<h3>Polymer Codelabs</h3>

<p><a href="http://www.code-labs.io/polymer-summit" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer Codelabs</a>は、Polymerを使ったアプリケーションを作成していくチュートリアル集です。Polymer Summitに合わせて11個のチュートリアルが公開されています。こちらも英語ですが、環境のセットアップから丁寧に解説されています。</p>

<h3>セッション動画リスト</h3>

<p>Polymer Summit 2015の各セッションはすべて以下のチャンネルで配信されていますので、興味のある方は是非チェックしてみてください。</p>


<!-- iframe plugin v.4.3 wordpress.org/plugins/iframe/ -->
<iframe width="560" height="315" src="https://www.youtube.com/embed/jVn8tlnwAEs?list=PLNYkxOF6rcICdISJclfQhj2S8QZGjXV8J" frameborder="0" 0="allowfullscreen" scrolling="yes" class="iframe-class"></iframe>

]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>Web ComponentsのこれからーPolymer 0.8、X-Tag、Brick、Bosonic</title>
		<link>/1000ch/14400/</link>
		<pubDate>Thu, 07 May 2015 00:00:38 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Polymer]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=14400</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (5)この記事は、連載「基礎からわかるWeb Components徹底解説～仕様から実装まで理解する〜」の第5回目になります。...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (5)</div><p>この記事は、連載「基礎からわかるWeb Components徹底解説～仕様から実装まで理解する〜」の第5回目になります。今回は、先日発表されたPolymer 0.8の変更点、MozillaのWeb Componentsへの関わり方やX-Tag、Brick、BosonicといったPolymer以外の周辺ライブラリについて紹介します。</p>

<h2>Polymer 0.8のリリース</h2>

<p>先日、Polymerのバージョン0.8がリリースされました。</p>

<ul>
<li><a href="https://blog.polymer-project.org/updates/2015/03/27/why-0.8/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">0.8 Released! &#8211; Polymer Blog</a></li>
</ul>

<p>0.8は1.0のリリースに向けたアルファリリースと位置付けられています。APIも正式版への提案として大きく変更が加えられており、これまでとの互換性も保たれていません。既に実践でPolymerを使っていて、0.8へアップデートを行う場合はこれまでのコンポーネントを作り直す必要があります。</p>

<p>アルファ版ということで、ユーザーのフィードバックや再設計を経て、さらなる変更がある可能性もありますが、より完成度の高いライブラリに近づいていくことでしょう。アップデートに伴い、どのような変更があるのかを見ていきます。</p>

<h3>ポリフィルライブラリ</h3>

<p>これまでポリフィルライブラリとして推奨されてきた<code>webcomponents.js</code>ですが、以降は<code>webcomponents-lite.js</code>が推奨されています。<code>webcomponents.js</code>と<code>webcomponents-lite.js</code>の差はShadow DOMのポリフィルを含んでいるかどうかなのですが、Polymer 0.8ではよりシンプルで軽量な <strong>shady DOM</strong> というShadow DOMのポリフィルを含んでいるためです。</p>

<ul>
<li><a href="https://github.com/webcomponents/webcomponentsjs" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">webcomponents/webcomponentsjs</a></li>
<li><a href="https://github.com/webcomponents/webcomponents-lite" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">webcomponents/webcomponents-lite</a></li>
</ul>

<p>異なる用途でShadow DOMのポリフィルを必要とする場合は別ですが、Polymerを利用するためであれば、<code>webcomponents.js</code>の重いShadow DOMのポリフィルを含まず、軽量な<code>webcomponents-lite.js</code>を選択するのが望ましいと言えます。</p>

<h3>軽量化とパフォーマンスの向上</h3>

<p>APIの大きな見直し（後述）と抜本的なリファクタリングによって、ライブラリのファイルサイズの軽量化と実行パフォーマンスの向上が図られています。以下は<a href="http://polymerlabs.github.io/benchmarks/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PolymerLabs/benchmarks</a>で試すことができるベンチマークの結果です（Polymer公式サイトより引用）。初期描画までの時間の棒グラフになっており、低ければ低いほど描画までの時間が短くパフォーマンスが良いということになります。</p>

<p><img src="/wp-content/uploads/2015/04/benchmark-640x480.png" alt="benchmark" width="640" height="480" class="alignnone size-large wp-image-14436" srcset="/wp-content/uploads/2015/04/benchmark.png 640w, /wp-content/uploads/2015/04/benchmark-300x225.png 300w, /wp-content/uploads/2015/04/benchmark-207x155.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<p>いくつかのデバイス・ブラウザで計測されていますが、いずれも0.5に比べて0.8が数倍高速に動作するという結果が出ています。もとよりWeb ComponentsはHTML ImportsのCustom Elementsの遅延評価されるという性質によるコンポーネントのがたつきやチラつきが問題になりがちですが、最も重要である初期描画までのパフォーマンスが向上されているのは嬉しいところです。</p>

<h2>互換性のない大きなAPIの変更</h2>

<p>冒頭で述べた通り、0.5以前と互換性はありません。</p>

<ul>
<li><a href="https://www.polymer-project.org/0.8/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">About this release &#8211; Polymer</a></li>
<li><a href="https://www.polymer-project.org/0.8/docs/migration.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Migration guide &#8211; Polymer</a></li>
</ul>

<p>リリースノートとマイグレーションガイドを元に主な変更点を見ていきます。さらなる詳細な差分については、公式ドキュメントを確認してください。</p>

<h3>カスタム要素の登録</h3>

<p>これまでは、&lt;polymer-element&gt;要素を用いてカスタム要素の登録を行ってきました。</p>

<p></p><pre class="crayon-plain-tag">&lt;polymer-element name="my-element"&gt;
  &lt;template&gt;
    &lt;style&gt;
      div { color: red; }
    &lt;/style&gt;
    &lt;div&gt;Shadow DOM in My Element&lt;/div&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer();
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>0.8からは以下のように、&lt;dom-module&gt;を起点にした方法に変更されます。&lt;dom-module&gt;要素のIDに登録したいカスタム要素名（ここでは<code>my-element</code>）を指定し、<code>Polymer</code>コンストラクタでそのIDを参照しています。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="my-element"&gt;
  &lt;style&gt;
    div { color: red; }
  &lt;/style&gt;
  &lt;template&gt;
    &lt;div&gt;Shadow DOM in My Element&lt;/div&gt;
  &lt;/template&gt;
&lt;/dom-module&gt;

&lt;script&gt;
  var MyElement = Polymer({is: 'my-element'});
&lt;/script&gt;</pre><p></p>

<p><code>Polymer({is: 'my-element'});</code>の実行より前に、&lt;dom-module&gt;がロードされている必要があります。&lt;style&gt;タグを&lt;template&gt;の外に配置するようになった点にも注意して下さい。要素の継承についても同様に<code>extends</code>キーワードで行うことができますが、0.5以前では可能であったカスタム要素の継承は廃止され、&lt;button&gt;や&lt;textarea&gt;のようなビルドインのHTML要素のみになっています。また、これまでは<code>constructor</code>属性にコンストラクタ関数名を指定していましたが、0.8では属性が廃止されるとともに<code>Polymer()</code>がカスタム要素のコンストラクタを返却するようになりました。</p>

<p><code>document.registerElement</code>の第二引数に指定する<code>extends</code>の値にもカスタム要素名を指定することはできず、コンストラクタ関数の返却も<code>document.registerElement</code>が行います。いずれも素の挙動に近い振る舞いになったと言えるでしょう。</p>

<h3>カスタム要素のパースの順序</h3>

<p>カスタム要素を含むカスタム要素を作成する場合は、内包するカスタム要素が事前に登録されている必要があります。つまり、&lt;child-element&gt;を含む&lt;parent-element&gt;を作成する場合、&lt;child-element&gt;が事前に登録されていなければなりません。</p>

<h3>デフォルト属性とプロパティの宣言</h3>

<p>カスタム要素のデフォルト属性とプロパティ宣言の方法も変わります。0.5までは以下のように、デフォルト属性は&lt;polymer-element&gt;に、プロパティの宣言は<code>attributes</code>にスペース区切りで指定していました。</p>

<p></p><pre class="crayon-plain-tag">&lt;polymer-element name="your-element" attributes="foo bar" role="button" layout horizontal wrap&gt;
  &lt;template&gt;
    &lt;button&gt;Your Element&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      foo: 0,
      bar: 'Hello!'
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>0.8以降は以下のように、<code>Polymer()</code>コンストラクタに<code>hostAttributes</code>や<code>properties</code>を渡すことで宣言します。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="your-element"&gt;
  &lt;template&gt;
    &lt;button&gt;Your Element&lt;/button&gt;
  &lt;/template&gt;
&lt;/dom-module&gt;

&lt;script&gt;
  Polymer({
    is: 'your-element',
    hostAttributes: {
      role: 'button',
      class: 'layout horizontal wrap'
    },
    properties: {
      foo: {
        type: Number,
        value: 0
      },
      bar: {
        type: String,
        value: 'Hello!'
      }
    }
  });
&lt;/script&gt;</pre><p></p>

<p><code>layout</code>、<code>horizontal</code>、<code>wrap</code>といったようなレイアウトに関するカスタム属性も通常のCSSクラスになり、<code>hostAttributes</code>の<code>class</code>に指定します。また、このレイアウト機能はPolymerのコアから分離され<code>polymer.html</code>に同梱されなくなったので、別途<code>layout.html</code>をインポートする必要があります。</p>

<h3>オブザーバーの登録</h3>

<p>プロパティの値の監視や、宣言的なイベントハンドラ登録といった機能もPolymerの強力な機能のひとつですが、これらにも少々変更があります。</p>

<p>これまでは、<code>foo</code>というプロパティに対して<code>fooChanged</code>というオブザーバー関数を宣言すると命名規則によって自動でバインドされる機能がありました。また、IDを振った要素は<code>this.$.id</code>という形でコンストラクタ内でノードを参照することが可能であり、それを<code>observers</code>に定義することでもプロパティの監視が可能でした。</p>

<p></p><pre class="crayon-plain-tag">&lt;polymer-element name="your-element" attributes="foo"&gt;
  &lt;template&gt;
    &lt;input type="text" id="input"&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      foo: 0,
      observers: {
        'this.&amp;.input.value': 'inputValueChanged'
      },
      fooChanged: function (oldValue, newValue) {
        console.log('newValue is ', newValue);
      },
      inputValueChanged: function () {
        console.log('this.&amp;.input.value is changed');
      }
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>0.8からはこの<code>propertyName</code>に対して<code>propertyNameChanged</code>という関数を定義することで自動バインドされる仕組みと、IDを振った要素が<code>this.$</code>にぶら下がる機能が廃止されます。これによってプロパティの監視を行うには、以下のように、ブラケットで囲った変数に対し、<code>observer</code>属性にオブザーバー関数を明示的に宣言することになります。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="your-element"&gt;
  &lt;template&gt;
    &lt;input type="text" value="{{inputValue}}"&gt;
  &lt;/template&gt;
&lt;/dom-module&gt;

&lt;script&gt;
  Polymer({
    is: 'your-element',
    properties: {
      foo: {
        type: Number,
        value: 0,
        observer: 'fooChanged'
      },
      inputValue: {
        observer: 'inputValueChanged'
      }
    },
    fooChanged: function (oldValue, newValue) {
      console.log('newValue is ', newValue);
    },
    inputValueChanged: function () {
      console.log('inputValue is changed');
    }
  });
&lt;/script&gt;</pre><p></p>

<p>暗黙的にオブザーバー関数がバインドされる分、直感的に解り難い機能であったので、このように明示的な宣言方法になるのは嬉しいところです。</p>

<h3>イベントハンドラの登録とデータバインディング</h3>

<p>ブラケット<code>{{}}</code>によるデータバインディングや、イベントハンドラの登録もPolymerお馴染みの機能ですが、これらにも少々変更があります。</p>

<p></p><pre class="crayon-plain-tag">&lt;polymer-element name="our-element"&gt;
  &lt;template&gt;
    &lt;input type="text" value="{{firstName}}"&gt;
    &lt;input type="text" value="{{lastName}}"&gt;
    &lt;button on-click="{{onClick}}"&gt;Say full name&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      onClick: function () {
        alert("{{firstName + ' ' + lastName}}");
      }
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>0.8からは<code>{{}}</code>内の式の評価はサポートされなくなります。</p>

<p>この例で言えば、<code>"{{firstName + ' ' + lastName}}"</code>のような表現はできなくなります。代わりに<code>computed</code>を用いて、式評価後の値を参照するプロパティを宣言する必要があります。これは0.5以前とは異なり、0.8からは<code>properties</code>の配下に定義する属性になることに注意してください。</p>

<p>同じく、<code>{{}}</code>を使って<code>on-click="{{onClick}}"</code>のように定義していたイベントハンドラの登録は、カーリーブラケットを使わずに記述します。</p>

<p></p><pre class="crayon-plain-tag">&lt;dom-module id="our-element"&gt;
  &lt;template&gt;
    &lt;input type="text" value="{{firstName}}"&gt;
    &lt;input type="text" value="{{lastName}}"&gt;
    &lt;button on-click="onClick"&gt;Say full name&lt;/button&gt;
  &lt;/template&gt;
&lt;/dom-module&gt;

&lt;script&gt;
  Polymer({
    is: 'our-element',
    properties: {
      firstName: String,
      lastName: String,
      fullName: {
        type: String,
        computed: 'computeFullName(firstName, lastName)'
      }
    },
    computeFullName: function (firstName, lastName) {
      return firstName + ' ' + lastName;
    },
    onClick: function () {
      alert('{{fullName}}');
    }
  });
&lt;/script&gt;</pre><p></p>

<p>このように<code>fullName</code>に<code>computed: computeFullName(firstName, lastName)</code>とすることで<code>computeFullName</code>の実行結果を参照することが可能です。</p>

<h3>DOMの操作</h3>

<p>Polymer内でDOM操作を行う場合、通常のShadow Rootから実行するDOMのAPIではなく、<code>Polymer.dom</code>という専用のAPIを使う必要があります。</p>

<ul>
<li><code>Polymer.dom(parent).appendChild(node)</code></li>
<li><code>Polymer.dom(parent).insertBefore(node, beforeNode)</code></li>
<li><code>Polymer.dom(parent).removeChild(node)</code></li>
<li><code>Polymer.dom(parent).querySelector(selector)</code></li>
<li><code>Polymer.dom(parent).querySelectorAll(selector)</code></li>
<li><code>Polymer.dom(parent).childNodes</code></li>
<li><code>Polymer.dom(node).parentNode</code></li>
<li><code>Polymer.dom(contentElement).getDistributedNodes()</code></li>
<li><code>Polymer.dom(node).getDestinationInsertionPoints()</code></li>
</ul>

<p><code>Polymer.dom</code>から行う追加や削除といったDOM操作は、パフォーマンスを考慮し遅延して実行するようになっています。そのため、操作後のノードの座標や<code>getComputedStyle()</code>を使ったスタイルの取得をする場合は、操作を<code>Polymer.dom.flush()</code>を使って適時実行し、反映します。</p>

<p></p><pre class="crayon-plain-tag">// 従来のDOM操作
this.appendChild(node);
this.shadowRoot.appendChild(node);

// Polymer.domを使ったDOM操作
Polymer.dom(this).appendChild(node);
Polymer.dom(this.root).appendChild(node);</pre><p></p>

<p>いずれもDOMのAPIと同じ命名と引数で設計されていますが、サブセットとして用意されているに過ぎず、完全に互換性があるわけではありません。例えば、<code>firstChild</code>といったプロパティは用意されていないので、この場合は代わりに<code>childNodes[0]</code>を使ってください。</p>

<h2>MozillaのWeb Componentsへの関わり方</h2>

<p>MozillaもWeb Componentsに対し、積極的な姿勢を見せています。FirefoxでもWeb Componentsの仕様のうち、いくつかが実験的に実装され、またX-TagやBrickといったWeb Componentsをつかったコンポーネント作成を後押しするライブラリもリリースしています。</p>

<h3>X-Tag</h3>

<p><a href="https://github.com/x-tag" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">X-Tag</a>はWeb Componentsの作成をサポートするライブラリです。<code>webcomponents.js</code>をポリフィルとし、記述の簡略化や一元化いったライブラリとしての目的もPolymerと似ている部分がありますが、Polymerに比べて多くのブラウザをサポートしているという特徴があります。X-Tagのブラウザターゲットは以下の通りです。</p>

<ul>
<li>Firefox 5+ desktop &amp; mobile</li>
<li>Chrome 4+, Android 2.1+</li>
<li>Safari 4+ desktop &amp; mobile</li>
<li>Internet Explorer 9+</li>
<li>Opera 11+ desktop &amp; mobile</li>
</ul>

<p>以下は公式ドキュメント引用のX-Tagを使ったカスタム要素作成のコード例です。Polymerほど多機能ではない分、シンプルで全体像を把握しやすいかもしれません。</p>

<p></p><pre class="crayon-plain-tag">xtag.register('x-accordion', {
  // extend existing elements
  extends: 'div',
  lifecycle:{
    created: function(){
      // fired once at the time a component
      // is initially created or parsed
    },
    inserted: function(){
      // fired each time a component
      // is inserted into the DOM
    },
    removed: function(){
      // fired each time an element
      // is removed from DOM
    },
    attributeChanged: function(){
      // fired when attributes are set
    }
  },
  events: {
    'click:delegate(x-toggler)': function(){
      // activate a clicked toggler
    }
  },
  accessors: {
    'togglers': {
      get: function(){
        // return all toggler children
      },
      set: function(value){
        // set the toggler children
      }
    }
  },
  methods: {
    nextToggler: function(){
      // activate the next toggler
    },
    previousToggler: function(){
      // activate the previous toggler
    }
  }
});</pre><p></p>

<h3>Brick</h3>

<p>X-Tagだけでなく、UIコンポーネント群として配布しているのが<a href="http://mozbrick.github.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Brick</a>です。PolymerとX-Tagとの関係になぞらえるなら、BrickはCore ElementsやPaper Elementsと同じような存在と言えます。以前まではX-Tagが使われていましたが、現在は非依存になっています。</p>

<p>BrickもPolymerの対抗馬として注目されていましたが、開発が中断されているのか、最近は更新がない状態です。参照しているポリフィルライブラリも<code>webcomponents.js</code>ではなく、旧称の<code>platform.js</code>になっているので、利用する場合は注意してください。</p>

<h3>Web Componentsの各仕様へのMozillaの対応</h3>

<p>2014年12月の記事ですが、MozillaのWeb Componentsに対する興味深い記事が公開されました。</p>

<ul>
<li><a href="https://hacks.mozilla.org/2014/12/mozilla-and-web-components/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Mozilla and Web Components: Update</a></li>
</ul>

<p>記事には、端的に述べると <strong>「Custom ElementsとShadow DOMの実装は進め、HTML Importsの実装は見送ります。差し当たって、HTML Importsの機能はJavaScriptのライブラリでやってください。」</strong> とあります。HTML Importsが実装されないことについての議論がコメント欄にて行われていますが、この記事の筆者である<a href="https://annevankesteren.nl/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Anne van Kesteren</a>は以下のように述べています。</p>

<p>&gt; Both ES6 modules and service workers open up resource dependency management to web developers. And while ES6 modules is mostly intended for JavaScript, we want to see what kind of dependency systems will be built on these two systems in libraries and frameworks before committing to a standardized design. Especially before committing to one that is not influenced by them. <strong>（ES6 modulesやService Workersも依存関係の解決に関わってくる。ES6 modulesはJavaScript向けのものだし、ライブラリやフレームワークにとってどのような機構になるかを見たい）</strong></p>

<p>依存管理の仕組みが、HTML ImportsとES6 modules、Service Workerのような、Web WorkersのimportScriptsといった方法が複数存在してしまうことを懸念してのことと察します。例えばHTML ImportsとES6 modulesは目的を共有するものではないはずですが、HTML ImportsでロードするHTML内でES6 modulesが使われるようなケースも加味して、様子を見たいということでしょう。</p>

<h2>Bosonic</h2>

<p>BosonicもPolymerやX-Tag同様に、Web Componentsの作成を支援するライブラリです。</p>

<ul>
<li><a href="http://bosonic.github.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Bosonic Web Component</a></li>
<li><a href="https://github.com/bosonic/bosonic" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">bosonic/bosonic &#8211; GitHub</a></li>
</ul>

<p>PolymerやX-Tagと異なるのは、これらのようにライブラリとして機能を補完したりするのではなく、作成したWeb ComponentsをBosonicを使って事前にビルドするトランスパイラであるという点です。Bosonicでビルドすることで、Internet ExplorerやSafariを含めたWeb Componentsに関するAPIが実装されていないブラウザでも機能するコードが生成されます。</p>

<p>Bosonicはブラウザターゲットは以下の通りです。</p>

<ul>
<li>Internet Explorer 9, 10, 11</li>
<li>Safari 8</li>
<li>Android 4.4</li>
<li>iOS 8.1</li>
<li>Chrome 35</li>
<li>Firefox 30</li>
</ul>

<h3>Bosonicで生成するコード例</h3>

<p>以下は公式で例示されている&lt;b-hello-world&gt;というWeb ComponentsをBosonicを使ってビルドする例です。</p>

<p></p><pre class="crayon-plain-tag">&lt;element name="b-hello-world"&gt;
  &lt;style&gt;
    :host {
      text-align: center;
      font-weight: bold;
      color: red;
    }
  &lt;/style&gt;
  &lt;template&gt;
    &lt;p&gt;Hello world!&lt;/p&gt;
  &lt;/template&gt;
  &lt;script&gt;
    ({
      createdCallback: function() {
        var root = this.createShadowRoot();
        root.appendChild(this.template.content.cloneNode(true));
      }
    });
  &lt;/script&gt;
&lt;/element&gt;</pre><p></p>

<p>Bosonicを使ってコンパイルすると、以下の様なCSSとJavaScriptのコードが出力されます。</p>

<p></p><pre class="crayon-plain-tag">b-hello-world {
  text-align: center;
  font-weight: bold;
  color: red;
}</pre><p></p>

<p></p><pre class="crayon-plain-tag">(function () {
  var BHelloWorldPrototype = Object.create(HTMLElement.prototype, {
    createdCallback: {
      enumerable: true,
      value: function () {
        var root = this.createShadowRoot();
        root.appendChild(this.template.content.cloneNode(true));
      }
    }
  });
  window.BHelloWorld = document.registerElement('b-hello-world', { prototype: BHelloWorldPrototype });
  Object.defineProperty(BHelloWorld.prototype, '_super', {
    enumerable: false,
    writable: false,
    configurable: false,
    value: HTMLElement.prototype
  });
  Object.defineProperty(BHelloWorldPrototype, 'template', {
    get: function () {
      var fragment = document.createDocumentFragment();
      var div = fragment.appendChild(document.createElement('div'));
      div.innerHTML = ' &lt;p&gt;Hello world!&lt;/p&gt; ';
      while (child = div.firstChild) {
        fragment.insertBefore(child, div);
      }
      fragment.removeChild(div);
      return { content: fragment };
    }
  });
}());</pre><p></p>

<p>これらのファイルと、いくつかのポリフィル（HTMLImports、MutationObservers、WeakMap、Custom Elements）をHTMLでロードすることで、非対応のブラウザでも&lt;b-hello-world&gt;を使うことができます。</p>

<h3>トランスパイラとして導入するメリット</h3>

<p>新しいAPIで書かれたコードを、非対応のブラウザ向けに従来のAPIで実行できるようにするというのは、ES6を<a href="https://babeljs.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Babel</a>でES5に変換するというアプローチと同様です。新しい技術の導入は常にブラウザサポートと天秤にかかり、サポートを優先するが故に新しい技術を試すことができないというジレンマも多いでしょう。しかし、こういったトランスパイラによって、新しい技術の導入と古いブラウザのサポートが、コストをかけずに両立できるのは非常に嬉しいところなのではないでしょうか。</p>

<p>&lt;element&gt;タグを使った古い記述であったり、リポジトリの更新もしばらくされていないなど、使うには少々抵抗があります。しかし、トランスパイラというアプローチは非常に興味深いものがありますので、メンテナンスが再開されるのを期待したいところです。</p>

<h2>まとめ</h2>

<p>Polymer 0.8のアップデート、X-Tag・Brick・Bosonic、およびFirefoxの実装状況について紹介しました。</p>

<p>ブラウザの実装状況は、Internet Explorer・Safariが未だに思わしくありません。仕様についても、FirefoxがHTML Importsの実装を遅らせたようにまだ確実とはいえない状況です。しかしこれは議論が行われている表れでもあり、Polymerの0.8がリリースされたことも含めて、Web Componentsに関する技術は普及に向けて着実に前進しているといえます。</p>

<p>しかし、Shadow DOMによってCSSやJSがカプセル化されても、今度はコンポーネント化のアプローチについて頭を悩ませることになるでしょう。Web Componentsが一般化するには仕様の安定やブラウザのサポートだけではなく、我々開発者でナレッジを蓄積していくことが最も重要です。</p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>基本的な要素・機能を提供するCore ElementsとMaterial Designを実現するPaper Elements</title>
		<link>/1000ch/12477/</link>
		<pubDate>Thu, 12 Feb 2015 04:00:00 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Material Design]]></category>
		<category><![CDATA[Polymer]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=12477</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (4)この記事は、連載「基礎からわかるWeb Components徹底解説～仕様から実装まで理解する〜」の第4回目になります。...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (4)</div><p>この記事は、連載「<a href="https://html5experts.jp/series/web-components-2/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">基礎からわかるWeb Components徹底解説～仕様から実装まで理解する〜</a>」の第4回目になります。今回は、前回紹介したGoogleが開発するWeb Componentsのライブラリ、Polymerを元に作られたコンポーネント群「Core Elements」と「Paper Elements」について紹介します。</p>

<h2>Core ElementsとPaper Elements</h2>

<p>Core ElementsとPaper ElementsはGoogleが開発するWeb Components群です。Core Elementsは、Webを構成する要素をWeb Componentsとして切り出し抽象化したものであり、Paper Elementsはデザインコンセプト<a href="http://www.google.com/design/spec/material-design/introduction.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Material Design</a>をWebで実現します。</p>

<p>いずれもPolymerをベースに作られている他、各コンポーネントが異なるコンポーネントに依存したつくりになっているものもあります。アーカイブファイルの配布もされていますが、<a href="http://bower.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Bower</a>であればインストール時に依存関係は自動で解決されるため、意識する必要はありません。</p>

<h2>インストールして実際に利用する例</h2>

<p>Core Elementsのひとつである<a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-image" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>core-image</code></a>というコンポーネントを、Bowerを使ってインストールし、実際に使う例を以下に示します。<code>core-image</code>は通常の<code>img</code>タグにはない、画像のリサイズやプリロードといった機能を備えたWeb Componentsです。</p>

<p>まず、コマンドラインからBowerを使って<code>core-image</code>をインストールします。<code>bower.json</code>に依存情報を記録する場合は<code>--save</code>オプションを付けます。</p>

<p></p><pre class="crayon-plain-tag">$ bower install Polymer/core-image</pre><p></p>

<p>実行後は、デフォルトであれば<code>bower_components</code>フォルダ配下に<code>core-image</code>と、<code>core-image</code>が依存するサブリソースがダウンロードされています。<code>core-image</code>が依存するのはPolymer Coreのみですが、Polymer Coreが依存するリソースがさらに存在するので、それらもダウンロードされています。</p>

<p>ダウンロードした<code>core-image.html</code>をHTML内でロードすることで、<code>core-image</code>要素を使えるようになります。</p>

<p></p><pre class="crayon-plain-tag">&lt;html&gt;
  &lt;head&gt;
    &lt;link rel="import" href="bower_components/core-image/core-image.html"&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;core-image src="http://lorempixel.com/400/400"&gt;&lt;/core-image&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre><p></p>

<p>サンプルでは、ダウンロードされたPolymer Coreを参照していませんが、ロードしているcore-image.htmlの中でPolymer Coreのインポートが行われているため、改めてPolymer Coreを読み込む必要はありません。</p>

<p>Polymerチームは、依存管理にBowerを使うことを推奨しています。自身でWeb Componentsを作成し、公開する場合は依存するリソースを<code>bower.json</code>に書き、コンポーネント内のサブリソースへの参照も、Bowerが使われることを意識するとよいでしょう。</p>

<h2>基本的な要素・機能を提供するCore Elements</h2>

<p>Core Elementsは、ユーザーインターフェースの基礎です。よく使われるけどブラウザネイティブには提供されていない機能や、Polymerの強力な機能との橋渡しとなるコンポーネントを提供します。後述のPaper Elementsの土台になっているように、拡張前提のコンポーネントも多くあります。</p>

<ul>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer core elements</a> Polymer公式サイトのCore Elementsのページ</li>
<li><a href="http://www.polymer-project.org/components/core-elements/demo.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Core Elements Sampler</a> Core Elementsのサンプル集</li>
</ul>

<h3>UI関連のCore Elements</h3>

<p>UIに関するCore Elementsとしては、既存のHTMLでは提供されていないドロップダウンやオーバーレイといった汎用的なUIや、既存のUIの機能を補完しているコンポーネントが用意されています。</p>

<p>例えば、<a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-dropdown" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>core-dropdown-menu</code></a>を使ったドロップダウンが挙げられます。ドロップダウンのUIは今や当たり前のように見かけますが、要件が出る度にイチから実装していたケースも多いのではないでしょうか。</p>

<p>以下は、<code>core-dropdown-menu</code>と<code>core-menu</code>及び<code>core-item</code>を組み合わせて実装するドロップダウンメニューの例（公式サイトから引用）です。</p>

<p></p><pre class="crayon-plain-tag">&lt;core-dropdown-menu label="Choose a pastry"&gt;
  &lt;core-dropdown class="dropdown"&gt;
    &lt;core-selector&gt;
      &lt;core-item label="Croissant"&gt;&lt;/core-item&gt;
      &lt;core-item label="Donut"&gt;&lt;/core-item&gt;
      &lt;core-item label="Financier"&gt;&lt;/core-item&gt;
      &lt;core-item label="Madeleine"&gt;&lt;/core-item&gt;
    &lt;/core-selector&gt;
  &lt;/core-dropdown&gt;
&lt;/core-dropdown-menu&gt;</pre><p></p>

<p>他にも以下のようなインターフェースが切り出されています。</p>

<ul>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-collapse" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-collapse</a> 折りたたみコンテンツ</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-drag-drop" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-drag-drop</a> ドラッグアンドドロップ</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-menu" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-menu</a> メニューアイテム</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-overlay" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-overlay</a> 他のコンテンツに覆いかぶさるオーバーレイ</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-tooltip" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-tooltip</a> ホバー時に表示されるツールチップ</li>
</ul>

<p>機能を補完している例としては、<a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-input" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>core-input</code></a>があります。</p>

<p><code>core-input</code>は<code>is="core-input"</code>のように、ネイティブHTMLの拡張として利用します。拡張されたinput要素は、<code>aria-label</code>や<code>aria-disabled</code>といった<a href="https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">ARIA</a>で定義されるアクセシビリティを自動で保管したり、フォームとしてサブミットされた時の値を<code>committedValue</code>として取得できるようになります。</p>

<p>他には前述の<code>core-image</code>も、後者に該当すると言えるでしょう。</p>

<h3>アニメーション関連のCore Elements</h3>

<p>Core ElementsにはUIパーツだけでなく、アニメーションを抽象化したコンポーネント群があります。その中核を成すのが<a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-animation" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>core-animation</code></a>です。</p>

<p><code>core-animation</code>でアニメーションを定義し、<code>core-animation-keyframe</code>と<code>core-animation-prop</code>でアニメーションの具体的な挙動を指定します。以下は<code>core-animation</code>要素でアニメーションを定義する例（公式サイトから引用）です。</p>

<p></p><pre class="crayon-plain-tag">&lt;core-animation id="fadeout" duration="500"&gt;
  &lt;core-animation-keyframe&gt;
    &lt;core-animation-prop name="opacity" value="1"&gt;&lt;/core-animation-prop&gt;
  &lt;/core-animation-keyframe&gt;
  &lt;core-animation-keyframe&gt;
    &lt;core-animation-prop name="opacity" value="0"&gt;&lt;/core-animation-prop&gt;
  &lt;/core-animation-keyframe&gt;
&lt;/core-animation&gt;

&lt;div id="el"&gt;Fade me out&lt;/div&gt;

&lt;script&gt;
  var animation = document.getElementById('fadeout');
  animation.target = document.getElementById('el');
  animation.play();
&lt;/script&gt;</pre><p></p>

<p>ここではキーフレーム毎に透過度を変化させ、フェードアウトのアニメーションを宣言しています。そして、アニメーションさせたい要素を、<code>core-animation</code>の<code>target</code>属性に指定することでフェードアウトさせてます。これはJavaScriptのコードで命令的に行っています。</p>

<p>複数のアニメーションを組み合わせて使いたい場合は、<a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-animation-group" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>core-animation-group</code></a>というグルーピングを担うコンポーネントもあります。</p>

<h3>ブラウザAPI関連のCore Elements</h3>

<p>提供しているのは、汎用的なアイコンやユーザーインターフェースといったビジュアルに関わる部分に限りません。Core ElementsではAjaxやキーボードアクションのハンドリング等、見た目には直接関わらない機能もWeb Componentsとして抽象化しています。</p>

<ul>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-a11y-keys" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-a11y-keys</a> キー押下のハンドリング</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-ajax" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-ajax</a> Ajaxの定義及びハンドリング</li>
<li><a href="https://www.polymer-project.org/docs/elements/core-elements.html#core-localstorage" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">core-localstorage</a> ローカルストレージへのアクセス</li>
</ul>

<p>今まではJavaScriptからそれぞれのAPIを実行し制御していた機能ですが、これらによって、他の要素との連携がPolymerが提供するデータバインディング機能などで容易になる他、HTMLに記述することで宣言的に挙動を定義していくことが可能になります。</p>

<p>以下はデータバインディングを利用して<code>input</code>要素に入力した値をローカルストレージに保存する例です。</p>

<p></p><pre class="crayon-plain-tag">&lt;input type="text" value="{{value}}"&gt;
&lt;core-localstorage name="localstorage-key" value="{{value}}"&gt;&lt;/core-localstorage&gt;</pre><p></p>

<p><code>core-localstorage</code>の<code>value</code>属性の値がローカルストレージに保存されるので、<code>input</code>の<code>value</code>とバインドすることで入力値がそのまま保存されるようになります。今まではイチからJavaScriptで制御していたことを考えれば、よりわかりやすく、そしてグッと楽になっています。</p>

<h2>Material Designを実現するPaper Elements</h2>

<p>Paper ElementsはMaterial DesignをWebで実現するWeb Components群です。Polymer Coreへ依存している他、Core Elementsをベースに構成されているコンポーネントが多くを占めています。</p>

<p><img src="/wp-content/uploads/2015/02/paper-ripple.png" alt="paper-ripple" width="1136" height="885" class="alignnone size-full wp-image-12496" srcset="/wp-content/uploads/2015/02/paper-ripple.png 640w, /wp-content/uploads/2015/02/paper-ripple-300x233.png 300w, /wp-content/uploads/2015/02/paper-ripple-1024x797.png 1024w, /wp-content/uploads/2015/02/paper-ripple-207x161.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></p>

<p>Material Designについては、<a href="https://html5experts.jp/ahomu/9307/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">話題のMaterial DesignをWebで実現！Polymerで「Paper Elements」を試そう</a>という記事を一読するとよいでしょう。</p>

<ul>
<li><a href="https://www.polymer-project.org/docs/elements/paper-elements.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer paper elements</a> Polymer公式サイトのPaper Elementsのページ</li>
<li><a href="http://www.polymer-project.org/components/paper-elements/demo.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Paper Elements Sampler</a> Paper Elementsのサンプル集</li>
</ul>

<h3>UI関連のPaper Elements</h3>

<p>UI関連のPaper Elementsとしては、Core ElementsのUIコンポーネントにMaterial Designを取り入れた要素が多くあります。これは要素の一覧を見比べてもらうとわかるかと思います。</p>

<p>以下は<a href="https://www.polymer-project.org/docs/elements/paper-elements.html#paper-dropdown-menu" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>paper-dropdown-menu</code></a>の利用例です。</p>

<p></p><pre class="crayon-plain-tag">&lt;paper-dropdown-menu label="Your favorite pastry"&gt;
  &lt;paper-dropdown class="dropdown"&gt;
    &lt;core-menu class="menu"&gt;
      &lt;paper-item&gt;Croissant&lt;/paper-item&gt;
      &lt;paper-item&gt;Donut&lt;/paper-item&gt;
      &lt;paper-item&gt;Financier&lt;/paper-item&gt;
      &lt;paper-item&gt;Madeleine&lt;/paper-item&gt;
    &lt;/core-menu&gt;
  &lt;/paper-dropdown&gt;
&lt;/paper-dropdown-menu&gt;</pre><p></p>

<p>このように、利用方法もほぼ同じです。Paper Elementsとして独自に拡張しているのは、影やエフェクトのdurationなどのMaterial Designの表現に関わる部分だけとも捉えられます。</p>

<h3>Material Designを体現するエフェクト</h3>

<p>影の動きや波紋のようなインタラクションや、奥行きを表現するZ座標のルールといった、特徴的なMaterial Designのコンセプトを担うのは、以下のコンポーネントです。</p>

<ul>
<li><a href="https://www.polymer-project.org/docs/elements/paper-elements.html#paper-ripple" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">paper-ripple</a> クリックした位置から波紋が広がるエフェクト</li>
<li><a href="https://www.polymer-project.org/docs/elements/paper-elements.html#paper-spinner" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">paper-spinner</a> 円状のスピナー</li>
<li><a href="https://www.polymer-project.org/docs/elements/paper-elements.html#paper-shadow" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">paper-shadow</a> 紙で持ち上げられ影がついたようなエフェクト</li>
</ul>

<p>特に<code>paper-ripple</code>と<code>paper-shadow</code>はMaterial Designのコンセプトの中核を担っており、Paper Elementsのほとんどが、この2つのいずれかに（あるいは両方に）依存したコンポーネントになっています。</p>

<h2>paper-shadowを利用する例</h2>

<p>実際に<code>paper-shadow</code>を取り入れてみます。以下は<code>paper-shadow</code>を使わずにレイアウトしています。</p>

<p><img src="/wp-content/uploads/2015/02/lp-without-shadow-1024x797.png" alt="lp-without-shadow" width="1024" height="797" class="alignnone size-large wp-image-12559" srcset="/wp-content/uploads/2015/02/lp-without-shadow-1024x797.png 1024w, /wp-content/uploads/2015/02/lp-without-shadow-300x233.png 300w, /wp-content/uploads/2015/02/lp-without-shadow-207x161.png 207w, /wp-content/uploads/2015/02/lp-without-shadow.png 640w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>

<p><code>paper-shadow</code>を使うには、冒頭で解説したようにBowerを使ってインストールして、影を付けたい要素を<code>paper-shadow</code>タグで囲むだけです。</p>

<p></p><pre class="crayon-plain-tag">&lt;paper-shadow z="2"&gt;
  &lt;core-selector id="theme-selector" selected="default"&gt;
    &lt;paper-item name="arta"&gt;Arta&lt;/paper-item&gt;
    &lt;paper-item name="ascetic"&gt;Ascetic&lt;/paper-item&gt;
    &lt;paper-item name="color-brewer"&gt;Color Brewer&lt;/paper-item&gt;
    &lt;paper-item name="docco"&gt;Docco&lt;/paper-item&gt;
    ...
  &lt;/core-selector&gt;
&lt;/paper-shadow&gt;</pre><p></p>

<p>このコードの場合は<code>core-selector</code>を囲っていますが、Core Elementsである必要はありません。<code>div</code>でも<code>section</code>でも大丈夫です。適用すると以下のようになります。</p>

<p><img src="/wp-content/uploads/2015/02/lp-with-shadow-1024x797.png" alt="lp-with-shadow" width="1024" height="797" class="alignnone size-large wp-image-12560" srcset="/wp-content/uploads/2015/02/lp-with-shadow-1024x797.png 1024w, /wp-content/uploads/2015/02/lp-with-shadow-300x233.png 300w, /wp-content/uploads/2015/02/lp-with-shadow-207x161.png 207w, /wp-content/uploads/2015/02/lp-with-shadow.png 640w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>

<p><a href="http://1000ch.github.io/syntax-highlight" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">サンプルページ</a>では、簡単ではありますが他のPaper Elementsも使っています。Paper Elementsを取り入れる例としては、<a href="https://html5experts.jp/girlie_mac/12359/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PolymerでMaterial Designなチャットアプリを作ろう</a>という記事に、より実践的なチュートリアルがあります。</p>

<h2>Polymer Designer</h2>

<p>Core ElementsやPaper Elementsを試す手段として<a href="http://polymer-designer.appspot.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymer Designer</a>があります。</p>

<p><img src="/wp-content/uploads/2015/02/polymer-designer.png" alt="polymer-designer" width="1136" height="885" class="alignnone size-full wp-image-12478" srcset="/wp-content/uploads/2015/02/polymer-designer.png 640w, /wp-content/uploads/2015/02/polymer-designer-300x233.png 300w, /wp-content/uploads/2015/02/polymer-designer-1024x797.png 1024w, /wp-content/uploads/2015/02/polymer-designer-207x161.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></p>

<p>Polymer Designerではブラウザ上で、Core ElementsやPaper Elementsをドラッグアンドドロップで配置し、データバインディング等もGUI上で行いコンポーネントを作成することが可能なツールです。Web上でお手軽に試したい場合には、こちらを使うのもよいでしょう。</p>

<p>使い方については<a href="https://html5experts.jp/1000ch/8906/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Componentsが変えるWeb開発の未来</a>という記事で触れています。</p>

<h2>おわりに</h2>

<p>Core ElementsとPaper Elementsの概要・使い方について紹介しました。Web Componentsを使ってWebページの基礎を組み立てるならCore Elementsは便利ですし、Material DesignをWebに取り入れるには、Paper Elementsが既にデファクトスタンダードと言えるかもしれません。</p>

<p>使う分にはもちろん便利なCore ElementsとPaper Elementsですが、興味があればGitHubで公開されているソースコードを見てみることをオススメします。どういった設計でどのように抽象化しているかを見るのも、今後Web Componentsを自分で作っていく上できっと参考になるはずです。</p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>Web Componentsを簡単・便利にするライブラリ「Polymer」を使いこなそう</title>
		<link>/1000ch/11905/</link>
		<pubDate>Fri, 26 Dec 2014 00:00:37 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Polymer]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=11905</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (3)Web Componentsを簡単・便利にするライブラリ「Polymer」を使いこなそう この記事は、連載「基礎からわか...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (3)</div><h1>Web Componentsを簡単・便利にするライブラリ「Polymer」を使いこなそう</h1>

<p>この記事は、連載「<a href="https://html5experts.jp/series/web-components-2/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a>」の第3回目になります。連載の第3回目となる今回は、Googleが中心となって開発を進めるPolymerというWeb Componentsのライブラリについて解説します。</p>

<h2>Web Componentsをより柔軟に、そして強力にするライブラリ</h2>

<p>Polymerは素のWeb Componentsにおいて、煩雑である部分を簡略化し、機能をより強力なものにし、基礎となるコンポーネントを提供します。<a href="http://ja.wikipedia.org/wiki/BSD%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">BSDライセンス</a>のもと、オープンソースで開発が行われており、ソースコードも<a href="https://github.com/Polymer" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">GitHub</a>にて公開されているので、Pull Requestを送るなどのかたちで私たちも開発に貢献することが可能です。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/12/polymer-project.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/12/polymer-project.png" alt="polymer-project" width="1136" height="885" class="alignnone size-full wp-image-11926" srcset="/wp-content/uploads/2014/12/polymer-project.png 640w, /wp-content/uploads/2014/12/polymer-project-300x233.png 300w, /wp-content/uploads/2014/12/polymer-project-1024x797.png 1024w, /wp-content/uploads/2014/12/polymer-project-207x161.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></a></p>

<ul>
<li><a href="https://www.polymer-project.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Welcome Polymer</a></li>
</ul>

<p>Polymerが提供する機能は、主に3つのパートに分かれています。Web Componentsの作成を簡単に、そして強力なものにする <strong>Polymer Core</strong> 。次に、一般的なUIの基礎の他、アニメーションや通信機能等を抽象化した <strong>Core Elements</strong> 。最後に、Google I/O 2014で発表されたデザインコンセプト「Material Design」をWebで実現する <strong>Paper Elements</strong> です。また、その他にも同じくPolymerチームが開発する、Web Componentsが未実装のブラウザでもWeb Componentsを利用可能にする WebComponents.js というポリフィルも提供されています。</p>

<p>Core Elements及びPaper Elementsは、Polymer Coreをベースに作られているため次回紹介するものとし、今回はPolymerの機能の中心となる <strong>Polymer Core</strong> と、この <strong>WebComponents.js</strong> について解説します。</p>

<h2>Polymer Core</h2>

<p>Polymer Coreは、素のWeb Componentsでは煩雑とも言える作成の手順を簡略化し、テンプレート機能やデータバインディングといった、Web Componentsを作る上で土台となる強力な機能を提供します。Core ElementsやPaper ElementsはこのPolymer Coreをベースに構成されています。よって、Core ElementsやPaper Elementsの利用にはPolymer Coreが必要です。</p>

<h3>カスタム要素の作成をシンプルに書き換える</h3>

<p>まずは、Web Componentsを作る最もシンプルな例を比べてみましょう。</p>

<p></p><pre class="crayon-plain-tag">&lt;template id='sample-element-template'&gt;
  &lt;button&gt;Sample Element&lt;/button&gt;
&lt;/template&gt;
&lt;script&gt;
  var SampleElementPrototype = Object.create(HTMLElement.prototype);
 
  SampleElementPrototype.createdCallback = function () {
    var shadowRoot = this.createShadowRoot();
    var template = document.querySelector('#sample-element-template');
    var clone = document.importNode(template.content, true);
    shadowRoot.appendChild(clone);
  };

  document.registerElement('sample-element', {
    prototype: SampleElementPrototype
  });
&lt;/script&gt;</pre><p></p>

<p>これはPolymerを使うことで以下のようにシンプルに書き換えることができます。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel="import" href="polymer.html"&gt;

&lt;polymer-element name="sample-element"&gt;
  &lt;template&gt;
    &lt;button&gt;Sample Element&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      ready: function () {}
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p><code>document.registerElement('sample-element')</code>によるカスタム要素の宣言は、Polymerの場合、&lt;polymer-element name=&#8221;sample-element&#8221;&gt;のように&lt;polymer-element&gt;の<code>name</code>属性によって行われます。</p>

<p>また、カスタム要素の雛形となる&lt;template&gt;からの要素のコピーとShadow Rootの作成を<code>createdCallback</code>で行っていた部分は、&lt;polymer-element&gt;が自動的に行うため省略できます。</p>

<p>このカスタム要素の定義のエントリポイントとなる&lt;polymer-element&gt;は、<code>polymer.html</code>をインポートすることで利用可能になります。</p>

<h3>&lt;polymer-element&gt;の属性値</h3>

<p>&lt;polymer-element&gt;固有の属性値として、次の4つがあります。</p>

<ul>
<li><code>name</code> 定義するカスタム要素名を指定する（必須）。</li>
<li><code>attributes</code> プロパティをスペース区切りで指定する（任意）</li>
<li><code>extends</code> 継承する他の要素名を指定する（任意）。<code>document.registerElement()</code>の第二引数に指定する<code>extends</code>と異なり、カスタム要素名を与えることが可能。</li>
<li><code>noscript</code> <code>Polymer()</code>をコールする必要がない場合に指定する（任意）</li>
<li><code>constructor</code> カスタム要素のコンストラクタ名を指定する（任意）。カスタム要素のコンストラクタは指定した名称でグローバル空間にエクスポートされる。<code>document.registerElement()</code>の返り値に指定する名称と同等。</li>
</ul>

<p>以下は<code>constructor</code>属性と<code>attributes</code>属性を指定する例です。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel="import" href="polymer.html"&gt;

&lt;polymer-element name="sample-element"
  constructor="SampleElement"
  attributes="foo bar baz"&gt;
  &lt;template&gt;
    &lt;button&gt;Sample Element&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      foo: 0,
      bar: 'Hello!'
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p><code>constructor="SampleElement"</code>と指定することで、このHTMLが評価されると、<code>new SampleElement()</code>でインスタンスを作成することができます。</p>

<p>また、<code>attributes="foo bar baz"</code>でプロパティを3つ定義しました。これは<code>Polymer()</code>関数内で初期値を指定しており、この例において<code>foo</code>、<code>bar</code>、<code>baz</code>の初期値はそれぞれ<code>0</code>、<code>'Hello!'</code>、<code>undefined</code>ということになります。</p>

<p>プロパティの宣言については、他にも<code>published</code>を使った方法があります。こちらに関してはPolymer公式の<a href="https://www.polymer-project.org/docs/polymer/polymer.html#attributes" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Published properties &#8211; API developer guide</a>を参照してください。</p>

<h3>ライフサイクルコールバック</h3>

<p>カスタム要素のプロトタイプに指定していたライフライクルコールバックは、それぞれ&#8221;Callback&#8221;が省略され、<code>Polymer()</code>に引数として指定します。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel="import" href="polymer.html"&gt;

&lt;polymer-element name="sample-element"&gt;
  &lt;template&gt;
    &lt;button&gt;Sample Element&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      created: function () {},
      attached: function () {},
      detached: function () {},
      attributeChanged: function () {}
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>また、素のCustom Elementsでは提供されていないPolymer特有のコールバックとして<code>ready</code>と<code>domReady</code>があります。<code>ready</code>は&lt;sample-element&gt;内で行っているShadow DOMの構築やイベントへのリスナの定義といった様々な処理が完了したタイミングで発火し、<code>domReady</code>は、宣言するカスタム要素に内包されるコンテンツの生成が完了したタイミングで発火します。</p>

<h3>テンプレート機能とデータバインディング</h3>

<p><a href="https://html.spec.whatwg.org/multipage/scripting.html#the-template-element" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Template</a>の仕様は不活性なHTML要素を提供するという非常にシンプルなものですが、Polymerは、テンプレートエンジン<a href="http://mustache.github.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Mustache</a>でお馴染みのブラケット<code>{{}}</code>を使ったテンプレート機能を提供します。</p>

<p></p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;ul&gt;
    &lt;template repeat="{{item in items}}"&gt;
      &lt;li&gt;{{item.name}} - {{item.message}}&lt;/li&gt;
    &lt;/template&gt;
  &lt;/ul&gt;
&lt;/template&gt;
&lt;script&gt;
  Polymer({
    ready: function () {
      this.items = [{
        name: 't32k',
        message: 'In Canada.'
      }, {
        name: 'hiloki',
        message: 'So hungry.'
      }, {
        name: '1000ch',
        message: 'Feeling lucky.'
      }];
    }
  });
&lt;/script&gt;</pre><p></p>

<p><code>{{variable}}</code>のように変数名を記述することで値が展開されるほか、コード例の<code>{{item in array}}</code>のように<code>in</code>を挿入することで、配列の要素を列挙することも可能です。この変数のコンテキストは常に作成するカスタム要素(<code>this</code>)になります。</p>

<p>また、この<code>{{}}</code>による変数の挿入は、次の<code>on-click="{{onClick}}"</code>のような書式でイベントハンドラの設定をする他、そのままデータバインディングにもなります。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel="import" href="bower_components/polymer/polymer.html"&gt;

&lt;polymer-element name="sample-element" attributes="message"&gt;
  &lt;template&gt;
    &lt;input type="text" value="{{message}}"&gt;
    &lt;button on-click="{{onClick}}"&gt;&lt;content&gt;&lt;/content&gt;&lt;/button&gt;
  &lt;/template&gt;
  &lt;script&gt;
    Polymer({
      message: '',
      onClick: function () {
        alert(this.message);
      }
    });
  &lt;/script&gt;
&lt;/polymer-element&gt;</pre><p></p>

<p>まず、<code>on-click="{{onClick}}"</code>によってボタンがクリックされた時の処理を指定し、この<code>onClick</code>で指定されるイベントハンドラは<code>Polymer()</code>コンストラクタを参照しています。もちろんここでも変数のコンテキストはカスタム要素自身（<code>this</code>）になっています。</p>

<p>次に&lt;input type=&#8221;text&#8221; value=&#8221;{{message}}&#8221;&gt;となっている部分に着目してください。&lt;input&gt;要素の入力値に対し<code>{{message}}</code>という変数を割り当てていますが、これはデータバインディングの力によって入力値が随時<code>message</code>変数に代入されるようになります。先ほどの<code>onClick</code>において、この<code>message</code>をアラートで表示するようにしていますが、<code>message</code>変数を介して`&lt;input&gt;要素への入力値が表示されることになります。</p>

<p>最後に、&lt;sample-element&gt;の属性値を宣言している<code>attributes</code>でも<code>message</code>という属性を定義しています。こちらも例外なくデータバインディングが適用され、&lt;sample-element message=&#8221;Input Value&#8221;&gt;Button&lt;/sample-element&gt;とすれば <strong>Input Value</strong> が初期値として表示されます。</p>

<h2>Web Componentsのポリフィルライブラリwebcomponents.js (旧platform.js)</h2>

<p>Web Componentsを実際のWebで使うには、Custom ElementsやHTML Importsといった機能が、仕様にそってブラウザに実装されている必要があります。各機能は2014年末の時点で、実装を先行して積極的に進めていたChrome(Version 39.0.2171.71)をはじめ、Opera(26.0.1656.32)、Firefox(34.0.5)はフラグを立てることでサポートされています。</p>

<p>しかしSafari、Internet Explorerは共に実装を見合わせている状態で、ユーザーの環境を考慮するとこれらのブラウザでもWeb Componentsが動くことが望ましいです。</p>

<p>こういった環境をサポートするのが、<code>webcomponents.js</code>というPolymerチームの開発するポリフィルライブラリです。これをロードすることで、Web Componentsに関する機能が未実装のブラウザでも利用することが可能になります。また、Mozillaが開発するWeb Componentsのライブラリである<a href="http://www.x-tags.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">X-Tag</a>やUIコンポーネント群である<a href="http://mozbrick.github.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Brick</a>もサブセットとして利用しています。</p>

<ul>
<li><a href="http://webcomponents.org/polyfills/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polyfills &#8211; WebComponents.org</a> <code>webcomponents.js</code>に関する解説</li>
<li><a href="https://github.com/WebComponents/webcomponentsjs" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">webcomponents/webcomponentsjs</a> GitHubのリポジトリ</li>
</ul>

<p>以前までは<code>platform.js</code>という名前で知られていましたが、<a href="https://blog.polymer-project.org/announcements/2014/10/16/platform-becomes-webcomponents/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Polymerの公式アナウンス</a>で<code>v0.5.0</code>から<code>webcomponents.js</code>への名称変更とGitHubのリポジトリが<a href="http://github.com/webcomponents" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Components</a>へ移管が発表されました。<a href="https://github.com/webcomponents/webcomponentsjs/commits/master" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">コミットログ</a>を見る限り開発体制も変わっていないように見えますので、役割に応じて分担を適切に図った結果と言えるでしょう。</p>

<h3>webcomponents.jsのインストールと利用方法</h3>

<p><code>webcomponents.js</code>はGitHubのリポジトリからダウンロードすることができます。</p>

<ul>
<li><a href="https://github.com/webcomponents/webcomponentsjs/releases" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Releases · webcomponents/webcomponentsjs</a></li>
</ul>

<p>Bowerもしくはnpmからもダウンロードすることが可能です。プロジェクトのパッケージ管理に合わせて選んでください。</p>

<p></p><pre class="crayon-plain-tag"># bowerでインストール
$ bower install --save webcomponentsjs

# npmでインストール
$ npm install --save webcomponents.js</pre><p></p>

<p>あとはダウンロードしてきた<code>webcomponents.js</code>をHTML内でロードするだけです。Web ComponentsのAPIを実行される前に評価されている必要があることに注意する必要があります。公式では、<a href="http://webcomponents.org/polyfills/#installation-usage" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">head要素で最初のscript要素としてロードすることを推奨しています</a>。</p>

<p></p><pre class="crayon-plain-tag">&lt;script src="bower_components/webcomponentsjs/webcomponents.js"&gt;&lt;/script&gt;</pre><p></p>

<p>また、<code>webcomponents.js</code>はWeb Componentsの仕様4つ全てをサポートしますが、Shadow DOMのポリフィルを除いた<code>webcomponents-lite.js</code>もあります。重いShadow DOMのサポートがない分、軽量化されているので、もしShadow DOMを必要としないのであれば<code>webcomponents-lite.js</code>を選択するのもよいでしょう。</p>

<h3>webcomponents.jsのカスタムビルド</h3>

<p>Web Componentsのポリフィルを提供する<code>webcomponents.js</code>ですが、<code>webcomponents-lite.js</code>のように補完する機能を選べるように、Custom Elements・HTML Imports・Shadow DOMそれぞれ個別のポリフィルを行うカスタムビルドが用意されています。先程の<code>bower</code>によるインストールで<code>CustomElements.js</code>、<code>HTMLImports.js</code>、<code>ShadowDOM.js</code>が同梱されているので、これらをロードすることで個別にポリフィルを行うことも可能です。</p>

<p>自身でカスタムビルドを行う場合はリポジトリをクローンし、<code>gulp</code>で用意されているカスタムビルドを実行します。</p>

<p></p><pre class="crayon-plain-tag"># リポジトリのクローン
$ git clone git@github.com:webcomponents/webcomponentsjs.git

# クローンしたリポジトリに移動
$ cd webcomponentsjs

# 依存モジュールのインストール
$ npm install

# gulpのbuildタスクを実行
$ gulp build</pre><p></p>

<p>実行後、<code>dist</code>フォルダにそれぞれのファイルが生成されます。ES6の機能であるWeakMapやMutationObserverもポリフィルとして用意され、Web Componentsに関する仕様であるCustom Elements、HTML Imports、Shadow DOMに、内部で使われているのは非常に興味深い点です。</p>

<h2>Polymerとwebcomponents.jsの取り扱い</h2>

<p>このように、Polymerはカスタム要素を構築する上での機能拡張であり、<code>webcomponents.js</code>はWeb Componentsの前提となる機能を提供します。よって、「Web Componentsを作る上でPolymerがなければ作れない」ということはありませんし、提供したいブラウザ環境にWeb Componentsの仕様が実装されていれば<code>webcomponents.js</code>は不要です。</p>

<p>説明した通りPolymerでは、素のWeb Componentsとは少々異なる方法論を強いられます。これは、Web Componentsの仕様が今後変わる場合に、PolymerがAPIのギャップを吸収してくれる可能性を示唆しています。ですが、Polymer自体のAPIの変更がある場合にそれに追従しなければならないですし、Web Componentsの仕様そのものに比べればサポートがいつまで行われるかという懸念もあります。これらを踏まえると、Web Componentsの機能をひと通り理解した上でPolymerを使うことが望ましいと言えるでしょう。</p>

<h2>まとめ</h2>

<p>Web Componentsの基礎仕様・実践的なコンポーネント作成に続き、今回はWeb Componentsを支えるPolymerについて解説しました。次回はPolymerを実際に使って作られた、便利な基礎コンポーネント群 <strong>Core Elements</strong> と、Material DesignをWebで実現する <strong>Paper Elements</strong> について扱います。</p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>Web Componentsでカルーセルギャラリーを作る─Web Components実践編</title>
		<link>/1000ch/11626/</link>
		<pubDate>Tue, 09 Dec 2014 00:00:07 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=11626</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (2)この記事は、連載「基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜」の第2回目になり...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (2)</div><p>この記事は、連載「<a href="https://html5experts.jp/series/web-components-2/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a>」の第2回目になります。</p>

<h2>カルーセルギャラリーをコンポーネント化する</h2>

<p>Web Componentsによってスコープが実現したものの、コンポーネント化する難しさは変わりません。分割しすぎればHTML ImportsによるHTTPリクエストが必然的に増加し、パフォーマンスへ懸念が残ります。コンポーネント化した要素のどの部分を変更可能にするかなどの、汎用性についても悩ましいところです。</p>

<p>複数の写真をコンパクトなスペースで表示するUIとして、カルーセルギャラリーはよく見かけるUIです。これを実装するには、例えばカルーセルを構築するためのJavaSciriptの処理、及びそのCSSを既存のHTMLに追加する必要があります。</p>

<p>カルーセルギャラリーを構成するためのjQueryのプラグインなどは多数配布されていますが、サードパーティのリソースを導入するリスクについては<a href="https://html5experts.jp/1000ch/11142/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">前回の記事</a>で触れたとおりです。今回は実践編として、これをコンポーネント化し、タグのみでカルーセルを実現する&lt;carousel-panel&gt;という要素を実際に作っていきます。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/12/carousel-panel.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/12/carousel-panel.png" alt="carousel-panel" width="1136" height="889" class="alignnone size-full wp-image-11711" srcset="/wp-content/uploads/2014/12/carousel-panel.png 640w, /wp-content/uploads/2014/12/carousel-panel-300x234.png 300w, /wp-content/uploads/2014/12/carousel-panel-1024x801.png 1024w, /wp-content/uploads/2014/12/carousel-panel-207x161.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></a></p>

<p>キャプチャだけではわかりませんが、画像をマウスで左右にスワイプ可能になっています。</p>

<h3>仕様の決定と利用のイメージ</h3>

<p>コンポーネントを作る前に、どのような機能でどのように表示されるかといった仕様を決めなければなりません。これはWeb Componentsに限らない話ですが、機能として断片化するにあたり重要なステップです。今回は次のような形で進めます。</p>

<ul>
<li>&lt;carousel-panel&gt;をカスタム要素とする</li>
<li>カルーセルに表示する画像は、内部の&lt;img&gt;に指定</li>
<li>&lt;img&gt;は複数配置可能だが、画像サイズは同一とする</li>
<li>スワイプアクションで画像を送れるようにする</li>
</ul>

<p>以下は配置するHTMLのイメージです。</p>

<p></p><pre class="crayon-plain-tag">&lt;carousel-panel&gt;
  &lt;img src="image.png" width="320" height="320"&gt;
  &lt;img src="image.jpg" width="320" height="320"&gt;
  &lt;img src="image.gif" width="320" height="320"&gt;
&lt;/carousel-panel&gt;</pre><p></p>

<p>このステップでは、HTMLタグとしてのインターフェースや、どんなUIを備えているのかといった実装の部分はもちろんのこと、切り出す機能の範囲やそもそもの利用頻度といった保守性についても考慮したほうがよいでしょう。</p>

<h2>カルーセルのベースとなる要素を作成する（Web Componentsの基本手順のおさらい）</h2>

<p>では、<a href="https://html5experts.jp/1000ch/11142/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">前回の記事</a>で解説した内容を踏まえて、次のような流れで作成していきます。</p>

<ol>
<li>カスタム要素の雛形となるHTMLを&lt;template&gt;に定義する</li>
<li>挙動を決定するプロトタイプオブジェクトを作成し、ライフサイクルコールバックを指定する</li>
<li>カスタム要素にShadow DOMを作成し、スタイルを閉じ込める</li>
<li>プロトタイプオブジェクトを<code>document.registerElement()</code>に指定し、カスタム要素を定義する</li>
</ol>

<p></p><pre class="crayon-plain-tag">&lt;template id='template-carousel-panel'&gt;&lt;/template&gt;

&lt;script&gt;
  // 現在実行中のスクリプト要素を取得する
  var currentScript = document.currentScript;
  var ownerDocument = currentScript.ownerDocument;
  var CarouselPanelPrototype = Object.create(HTMLElement.prototype);

  // &lt;carousel-panel&gt;要素が生成された時のコールバック
  CarouselPanelPrototype.createdCallback = function () {
    var template = currentScript.ownerDocument.querySelector('#template-carousel-panel');
    var clone = document.importNode(template.content, true);

    this.shadowRoot = this.createShadowRoot();
    this.shadowRoot.appendChild(clone);
  };

  // &lt;carousel-panel&gt;要素がHTMLに追加された時のコールバック
  CarouselPanelPrototype.attachedCallback = function () {};

  // &lt;carousel-panel&gt;要素の定義
  window.CarouselPanel = document.registerElement('carousel-panel', {
    prototype: CarouselPanelPrototype
  });
&lt;/script&gt;</pre><p></p>

<p>これで&lt;carousel-panel&gt;要素の定義と、処理を追加していく雛形の作成が完了しました。</p>

<h2>カルーセル要素へ挿入したコンテンツを利用する（Shadow DOMへのコンテンツ挿入）</h2>

<p>&lt;carousel-panel&gt;内に&lt;img&gt;を配置するという仕様にしました。しかし、Shadow Rootが生成されると、ブラウザに表示されるのはShadow Rootに追加された内容になります。</p>

<p>このようにカスタム要素で挟まれた要素（この場合は&lt;img&gt;）をShadow DOM内で参照するには&lt;content&gt;を使います。&lt;content&gt;を使ってShadow DOM内に挿入ポイントを設けることで、Shadow Hostのコンテンツを参照することが可能になります。</p>

<p>Shadow Rootに追加するHTMLの雛形となるテンプレートを以下のように変更します。</p>

<p></p><pre class="crayon-plain-tag">&lt;template id='template-carousel-panel'&gt;
  &lt;div id='container' class='carousel'&gt;
    &lt;div id='wrap' class='carousel-wrap'&gt;
      &lt;content&gt;&lt;/content&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</pre><p></p>

<p>すると以下のように、HTMLのツリー構造に変化はありませんが、Shadow Root配下にある&lt;content&gt;に&lt;img&gt;があるように振る舞います。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/11/content.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/11/content.png" alt="content" width="1136" height="960" class="alignnone size-full wp-image-11637" srcset="/wp-content/uploads/2014/11/content.png 640w, /wp-content/uploads/2014/11/content-300x253.png 300w, /wp-content/uploads/2014/11/content-1024x865.png 1024w, /wp-content/uploads/2014/11/content-207x174.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></a></p>

<p>&lt;content&gt;による挿入ポイントを複数定義し、参照をコントロールする手段も存在します。これらのShadow DOMのさらなるコンセプトについては、HTML5Rocksの<a href="http://www.html5rocks.com/ja/tutorials/webcomponents/shadowdom-301/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Shadow DOM 301 &#8211; 上級者向けコンセプトと DOM API</a>という記事を参照してください。</p>

<h2>カルーセル要素のスタイリング（Shadow DOMへ追加したコンテンツのスタイリング）</h2>

<p>次に、カルーセルギャラリーを実現するために&lt;carousel-panel&gt;に配置される&lt;img&gt;や、内部でラッパーの役割を果たしている&lt;div id=&#8217;container&#8217; class=&#8217;carousel&#8217;&gt;や&lt;div id=&#8217;wrap&#8217; class=&#8217;carousel-wrap&#8217;&gt;にCSSを適用します。</p>

<p>Shadow DOM内のHTMLを装飾する場合、Shadow DOMに&lt;style&gt;要素を追加することで、CSSの影響範囲はShadow Root配下のHTMLに限定されます。</p>

<p>しかし、カスタム要素の外からShadow DOMを装飾したいケースも往々にしてあることでしょう。そのために、カスタム要素の外からカスタム要素内のHTML（つまりShadow DOM）にアクセスしたり、前述の&lt;content&gt;によって参照されるHTMLにアクセスするためのCSSセレクタがあります。</p>

<h3>Shadow DOMに関連するCSSセレクタ</h3>

<p>今回はカスタム要素である&lt;carousel-panel&gt;と、&lt;content&gt;によって参照する要素の選択するCSSセレクタを使います。</p>

<ul>
<li><code>:host</code> Shadow Treeをホストしている要素（つまり、カスタム要素）を指すセレクタ</li>
<li><code>::content</code> &lt;content&gt;による挿入ポイントをShadow DOM内部から参照するセレクタ</li>
</ul>

<p><code>:host {background: red;}</code>というルールをShadow DOM内の&lt;style&gt;に追加すると、&lt;carousel-panel&gt;の背景色が赤になります。また、<code>:host()</code>のようにホスト要素に特定のクラスが付与されているケースだけに限定することも可能です。<code>:host(.red) {background: red;}</code>というルールを定義すると、&lt;carousel-panel class=&#8217;red&#8217;&gt;の場合のみ、背景色が赤になります。</p>

<p>また、&lt;content&gt;によって参照する要素は、Shadow Root配下に存在しません。そのため、Shadow DOM内部から参照するために<code>::content</code>という擬似要素が用意されています。</p>

<p>今回はこの<code>:host</code>で&lt;carousel-panel&gt;そのものと配下の<code>.carousel</code>と<code>.carousel-wrap</code>を、<code>::content</code>で&lt;carousel-panel&gt;のコンテンツとなる&lt;img&gt;を、それぞれスタイリングしています。</p>

<p></p><pre class="crayon-plain-tag">&lt;template id='template-carousel-panel'&gt;
  &lt;style&gt;
    :host {
      display: inline-block;
    }
    :host .carousel {
      overflow: hidden;
      visibility: hidden;
      position: relative;
    }
    :host .carousel-wrap {
      overflow: hidden;
      position: relative;
    }
    ::content img {
      float: left;
      position: relative;
    }
  &lt;/style&gt;
  &lt;div id='container' class='carousel'&gt;
    &lt;div id='wrap' class='carousel-wrap'&gt;
      &lt;content&gt;&lt;/content&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</pre><p></p>

<p>他にも、今回は使いませんが、Shadow DOMに関連するCSSセレクタは次のものがあります。</p>

<ul>
<li><code>:host-context()</code> ホスト要素の祖先にあたる要素を選択する</li>
<li><code>::shadow</code> Shadow DOMの外部からShadow DOMを参照する擬似要素</li>
<li><code>/deep/</code> Shadow DOMが形成するスコープを貫通するコンビネータ</li>
</ul>

<p>これらのShadow DOMに関するCSSセレクタについての詳細は、HTML5Rocksの<a href="http://www.html5rocks.com/ja/tutorials/webcomponents/shadowdom-201/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Shadow DOM 201 &#8211; CSS とスタイリング</a>が参考になるでしょう。</p>

<h3>Shadow DOM配下の&lt;link&gt;要素は無効になる</h3>

<p>Shadow DOM配下のHTMLにCSSを適用するために&lt;style&gt;を記述してきましたが、以下のように&lt;link rel=&#8217;stylesheet&#8217;&gt;ではダメなのかという疑問を抱いた人もいるかと思います。</p>

<p></p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;link rel='stylesheet' href='carousel-panel.css'&gt;
  &lt;div id='container' class='carousel'&gt;
    &lt;div id='wrap' class='carousel-wrap'&gt;
      &lt;content&gt;&lt;/content&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;</pre><p></p>

<p>結論から言えば、Shadow DOM配下の&lt;link&gt;要素は無効です。&lt;link&gt;の他にも、&lt;base&gt;要素がShadow DOM配下では不活性でなければならないと、仕様で定められています。これについての詳細は<a href="http://www.w3.org/TR/shadow-dom/#inert-html-elements" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">7.1 Inert HTML Elements &#8211; Shadow DOM</a>を見てください。</p>

<p>そのため、Shadow DOM配下のHTMLをスタイリングするためには、Shadow DOMに&lt;style&gt;要素を記述するか、Shadow DOMの外から前述の<code>::shadow</code>等のセレクタを使う必要があります。</p>

<h2>カルーセル要素のJavaScriptによる挙動の追加（ライフサイクルコールバックの利用）</h2>

<h3>attachedCallback</h3>

<p>カルーセルを実装するには、<code>touchstart</code>・<code>touchmove</code>・<code>touchend</code>を使ったスワイプアクションのハンドリングや、スワイプ領域を確保するための画像サイズを計算、現在何枚目の画像を表示しているか等を記憶する必要があります。</p>

<p>このように、カスタム要素へのイベントを定義することの負荷であったり、このケースの画像のサイズの計算のようにHTMLに挿入されるまで不確定な情報の取得、タイマーの実行のようなグローバルのリソースを必要とする処理等は、<code>createdCallback</code>中で行うのではなく、HTMLに追加されたタイミングで実行される<code>attachedCallback</code>で行う方が適切と言えます。</p>

<p>今回のカルーセルの実装では、画像のスワイプ時の実装等をこの<code>attachedCallback</code>内で実装しました。Web Components特有の実装というものはなく、DOMのAPIを使った実装が続くので、記事での解説は割愛します。なお、最後にも紹介しますが今回の&lt;carousel-panel autoslide&gt;はGitHubにて全てのソースコードが公開されています。<a href="https://github.com/1000ch/carousel-panel/blob/master/carousel-panel.html#L49-L150" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">attachedCallbackの実装はこちら</a>です。</p>

<h3>detachedCallback</h3>

<p><code>window</code>や<code>document</code>のようなグローバルオブジェクトのイベントを監視したり、<code>setInterval</code>のようなタイマーを実行するような場合は、<code>detachedCallback</code>でそれらを解放するのが望ましいでしょう。必要なくなりカスタム要素が削除されても、イベントハンドラが残り続け、ブラウザに負荷を及ぼしてしまうといったような可能性があります。</p>

<h3>attributeChangedCallback</h3>

<p>&lt;script&gt;要素の<code>src</code>属性のように、値が更新されると要素の振る舞いは即座に変わります。カスタム要素の属性値の変更を検知して、振る舞いをコントロールしたい場合は、<code>attributeChangedCallback</code>を利用します。</p>

<p>今回の&lt;carousel-panel&gt;には利用していませんが、例えば&lt;carousel-panel autoslide&gt;のように、「<code>autoslide</code>がある場合は自動で画像送りをする」という仕様だとすると、<code>autoslide</code>が追加された場合は自動で画像送りを開始し、削除された場合はそれを止めるという処理を実装しなければなりません。</p>

<p><code>attributeChangedCallback</code>に指定するコールバック関数は<code>attributeName</code>（変更された属性）, <code>oldValue</code>（変更前の値）, <code>newValue</code>（変更後の値）を引数に取るので、様々な利用ケースに対応することができるでしょう。</p>

<h2>カルーセル要素の完成（デモとソースコード）</h2>

<p>ここまでの解説を踏まえて、後は実際にJavaScriptでスワイプ時の動作などをコールバックを利用して定義するだけです。実際に作成した&lt;carousel-panel&gt;は<a href="https://github.com/1000ch/carousel-panel/blob/master/carousel-panel.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">こちら</a>です。JavaScriptのコードは少し長いので、GitHubのリポジトリから確認していただきたいと思います。</p>

<ul>
<li><a href="https://github.com/1000ch/carousel-panel" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">1000ch/carousel-panel &#8211; GitHub</a></li>
<li><a href="http://1000ch.github.io/carousel-panel" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">デモページ</a></li>
</ul>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/11/demo.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/11/demo.png" alt="demo" width="1136" height="889" class="alignnone size-full wp-image-11642" srcset="/wp-content/uploads/2014/11/demo.png 640w, /wp-content/uploads/2014/11/demo-300x234.png 300w, /wp-content/uploads/2014/11/demo-1024x801.png 1024w, /wp-content/uploads/2014/11/demo-207x161.png 207w" sizes="(max-width: 1136px) 100vw, 1136px" /></a></p>

<p>本記事では実装していなかったスワイプ時の処理等が、<code>attachedCallback</code>内で追加されています。</p>

<p>また以下のサイトでは、リッチなUIをひとつのタグで利用できるようにしたものから、ブラウザAPIをHTMLから宣言的に利用可能にするものまで、実に様々なアイデアがWeb Componentsとして公開されています。コンポーネント化のアイデアに詰まったときには眺めてみるのもよいでしょう。</p>

<ul>
<li><a href="http://customelements.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Custom Elements &#8211; a web components gallery for modern web apps</a></li>
<li><a href="http://component.kitchen/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Component Kitchen &#8211; The best ingredients for your web apps</a></li>
</ul>

<h2>まとめ</h2>

<p>今回はひとつの機能を実際にWeb Components化する例を解説しました。Web Componentsによってコンポーネント化をする手段は提供されましたが、コンポーネント化のアイデアや方法論については、引き続き開発者の間で議論がされていくことと思います。</p>

<p>次回は、Web Componentsをより柔軟に、そして強力に利用出来るようにするPolymerというライブラリについて紹介します。</p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
		<item>
		<title>Web Componentsを構成する4つの仕様 ー Web Components基礎編</title>
		<link>/1000ch/11142/</link>
		<pubDate>Fri, 31 Oct 2014 00:00:10 +0000</pubDate>
		<dc:creator><![CDATA[泉水翔吾]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Web Components]]></category>

		<guid isPermaLink="false">/?p=11142</guid>
		<description><![CDATA[連載： 基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜 (1)我々Web開発者がWeb Componentsという言葉を耳にしてから、もう2年程経ったでしょうか。Web Compon...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/web-components-2/" class="series-214" title="基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜" data-wpel-link="internal">基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜</a> (1)</div><p>我々Web開発者がWeb Componentsという言葉を耳にしてから、もう2年程経ったでしょうか。<a href="https://html5experts.jp/1000ch/8906/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Componentsが変えるWeb開発の未来</a>という記事に、「今のWeb開発がどのような課題を抱えているか、それをWeb Componentsがどう解決するか」を書きました。これを踏まえて、本連載ではWeb Componentsの仕様から実装、PolymerやX-TagといったWeb Componentsを支えるライブラリなどの周辺知識まで解説していきます。</p>

<h2>Web Componentsを支える4つの仕様</h2>

<p>連載第1回目となる本記事では、Web Componentsを支える4つの仕様について解説します。Web Componentsは以下の4つの独立した仕様から構成されます。</p>

<ul>
<li><a href="http://www.w3.org/TR/custom-elements/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><strong>Custom Elements</strong></a> &#8211; 独自のカスタム要素をユーザーが定義することを可能にする</li>
<li><a href="http://www.w3.org/TR/shadow-dom/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><strong>Shadow DOM</strong></a> &#8211; Shadow DOMという起点になる要素を提供し、HTMLにスコープを形成する</li>
<li><a href="https://html.spec.whatwg.org/multipage/scripting.html#the-template-element" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><strong>Templates</strong></a> &#8211; HTMLのテンプレート機能をブラウザネイティブに利用可能にする</li>
<li><a href="http://www.w3.org/TR/html-imports/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><strong>HTML Imports</strong></a> &#8211; 断片化したHTMLファイルをロードする</li>
</ul>

<p>つまり、Web Componentsはそれがひとつの仕様というわけではなく、これらの各機能を組み合わせてHTMLをコンポーネント化する技術のことを指します。よって、各仕様は独立した機能なので、ブラウザが実装していれば単独で利用することも可能です。今回はこれらの仕様について、順に解説していきます。また、記事中で実際のコードを取り扱っていますが、これらを実際に試す場合は、各機能の実装が進んでいるGoogle Chromeで行うことをオススメします。</p>

<h2>Custom Elements</h2>

<p>Custom Elementsは、ブラウザに新たな要素を定義する仕様です。CSSでのスタイリングやJavaScriptで付与するインタラクションといった特徴をまとめたカスタム要素として登録し、新たなネイティブ要素として利用可能にします。</p>

<h3>カスタム要素を定義する</h3>

<p>カスタム要素を新しく定義するには<code>document.registerElement</code>というDOMのAPIを使います。<code>document.registerElement</code>の第1引数に文字列でカスタム要素名を指定し、第2引数の指定でカスタム要素の挙動が決定されます。ネイティブで定義されているタグとの区別のために、カスタム要素名にはハイフンを含める必要があります。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;を定義する
document.registerElement('sample-element');</pre><p></p>

<p>これで<code>sample-element</code>という、カスタム要素を使うことができるようになりました。</p>

<h3>カスタム要素の挙動を設定する</h3>

<p>先程定義した<code>sample-element</code>は、何の機能も持たない要素です。この<code>sample-element</code>の挙動をカスタマイズするには、
<code>document.registerElement</code>の第2引数の<code>prototype</code>属性に指定します。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;のプロトタイプ
var SampleElementPrototype = Object.create(HTMLElement.prototype);

// &lt;sample-element&gt;を定義する
document.registerElement('sample-element', {
  prototype: SampleElementPrototype
});</pre><p></p>

<p>このように、<code>HTMLElement</code>を継承した<code>SampleElementPrototype</code>オブジェクトを生成し、<code>prototype</code>属性に指定します。
<code>HTMLElement</code>を継承することで、HTMLとして基本的な振る舞いをするようになります。挙動を更に細かく制御するために、
<code>SampleElementPrototype</code>には、ライフサイクルコールバックを指定することが可能です。ライフサイクルコールバックには以下の4つがあります。</p>

<table>
  <tr>
    <th>createdCallback</th>
    <td>要素が生成されたときに実行されるコールバック関数</td>
  </tr>
  <tr>
    <th>attachedCallback</th>
    <td>要素がHTMLに追加されたときに実行されるコールバック関数</td>
  </tr>
  <tr>
    <th>detachedCallback</th>
    <td>HTMLから要素が除かれたときに実行されるコールバック関数</td>
  </tr>
  <tr>
    <th>attributeChangedCallback</th>
    <td>属性変更時に実行されるコールバック関数</td>
  </tr>
</table>

<p>これらのライフサイクルコールバックを必要に応じて利用します。</p>

<h3>カスタム要素を利用する</h3>

<p><code>document.registerElement('sample-element')</code>を実行したあとは、定義したカスタム要素を実際に利用することが可能です。HTML上に<code>sample-element</code>タグを書くことでも利用可能ですし、以下のようにJavaScriptから生成することもできます。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;を生成する
var sampleElement = document.createElement('sample-element');

// 生成した&lt;sample-element&gt;をbodyに追加する
document.body.appendChild(sampleElement);</pre><p></p>

<p>また、<code>document.registerElement()</code>は定義したカスタム要素のコンストラクタ関数を返すので、それを<code>new</code>と共に実行することでも生成可能です。</p>

<p></p><pre class="crayon-plain-tag">// document.registerElementの返り値を変数に保持する
var SampleElement = document.registerElement('sample-element', {
  prototype: SampleElementPrototype
});

// &lt;sample-element&gt;を生成する
var sampleElement = new SampleElement();

// 生成した&lt;sample-element&gt;をbodyに追加する
document.body.appendChild(sampleElement);</pre><p></p>

<h3>既存の要素を拡張する</h3>

<p>カスタム要素の作成には<code>prototype</code>を利用して挙動をゼロから指定するほか、<code>extends</code>に拡張したい要素名を指定し、その要素の拡張機能を作成するという方法があります。</p>

<p></p><pre class="crayon-plain-tag">// &lt;button&gt;を拡張するextended-buttonを定義する。
document.registerElement('extended-button', {
  prototype: ExtendedButtonPrototype,
  extends: 'button'
});</pre><p></p>

<p><code>extends</code>を使って作成された要素を利用する場合は、<code>is='extended-button'</code>のように<code>extends</code>で指定した要素の<code>is</code>属性に指定します。</p>

<p></p><pre class="crayon-plain-tag">&lt;button is='extended-button'&gt;This is button&lt;/button&gt;</pre><p></p>

<p><code>extends</code>を使って作成された要素は、元々の要素の見た目や内部の特徴を持ちつつも、<code>is='〜'</code>で指定されたカスタム要素の特徴を持ちます。この場合、<code>button</code>要素に<code>extended-button</code>で定義した処理が付与されることになります。</p>

<p>また、<code>extends</code>属性にはカスタム要素を指定することはできません。<code>prototype</code>に指定するオブジェクトに継承させましょう。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;のプロトタイプ
var SampleElementPrototype = Object.create(HTMLElement.prototype);

// &lt;sample-element&gt;を定義する
document.registerElement('sample-element', {
  prototype: SampleElementPrototype
});

// これはNG。カスタム要素をextendsすることはできない
// 継承したい場合はSampleElementPrototypeを利用する
document.registerElement('extended-again-element', {
  prototype: Object.create(HTMLElement.prototype),
  extends: 'sample-element'
});</pre><p></p>

<h2>Shadow DOM</h2>

<p>Shadow DOMはHTMLの世界にスコープの概念をもたらします。HTMLとCSS、そしてJavaScriptを組み合わせて作ったUIコンポーネントの再利用を考えた時に必ず障壁となるのがスコープがないという問題でした。（正確に言えば、<code>iframe</code>だけはスコープを形成しますが、セキュリティが強く柔軟性に欠け、コンポーネント化という目的は果たせません）</p>

<p>例えばボタンのコンポーネントを作るために<code>.button</code>というクラスを定義しても、このCSSを別の場所で利用しようとした際に同名のクラスが定義されていると、どちらか一方が上書きされてしまいます。こうした問題に対しては命名規則の工夫等、様々なアプローチがされてきましたが、いずれもカスケーディングを完全に回避できる保証はありません。</p>

<p>これを根本的に解決してくれるのがShadow DOMです。</p>

<h3>Shadow DOMの仕組み</h3>

<p>Shadow DOMによって、要素は新たにShadow Rootという新たなノードを持つことができるようになります。このShadow Rootを持つ要素はShadow Hostと呼ばれ、スコープの起点となります。Shadow Rootにぶら下がるDOMツリーには外部からアクセスすることができず、内部の処理が外部に漏れることもありません。</p>

<p>つまり、 <strong>カスタム要素の振る舞いをShadow DOMに閉じ込めることで、既存のスタイルやJavaScriptに影響されずに扱う</strong> ことができます。</p>

<p>実は、Chromeでは既にネイティブのHTMLの要素に、Shadow DOMが使われています。代表的なのが<code>video</code>要素です。<code>video</code>要素のShadow DOMを確認するには、DevToolsのSettingsの「Show user agent shadow DOM」をチェックする必要があります。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/10/devtools-shadowdom.png" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><img src="/wp-content/uploads/2014/10/devtools-shadowdom-300x253.png" alt="Video要素で利用されているShadow DOM" width="300" height="253" class="alignnone size-medium wp-image-11149" srcset="/wp-content/uploads/2014/10/devtools-shadowdom-300x253.png 300w, /wp-content/uploads/2014/10/devtools-shadowdom-1024x865.png 1024w, /wp-content/uploads/2014/10/devtools-shadowdom-207x174.png 207w, /wp-content/uploads/2014/10/devtools-shadowdom.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>DevToolsで<code>video</code>要素を見てみると、<code>video</code>の下に <strong>#shadow-root</strong> があり、その下に<code>div</code>要素や<code>input</code>要素がぶら下がっているのが確認できます。<code>div</code>や<code>input</code>にフォーカスしてみると、それらが再生ボタン等のUIコントロール部分を構成しているのがわかると思います。このShadow Hostは<code>video</code>要素ということになります。</p>

<ul>
<li><a href="http://www.w3.org/2010/05/video/mediaevents.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">HTML5 Video Events and API</a></li>
</ul>

<p><code>video</code>要素の他にも、<code>textarea</code>や<code>input</code>等でネイティブで使われているShadow DOMを確認することが可能です。</p>

<h3>Shadow Rootの生成</h3>

<p>では実際にShadow DOMを利用していきます。Shadow Rootを生成するには<code>createShadowRoot()</code>というDOMのAPIを使います。
<code>createShadowRoot</code>はHTMLElementをインターフェースとするどの要素からも実行することが可能です。ここでは先程の<code>sample-element</code>要素内で使います。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;のプロトタイプ
var SampleElementPrototype = Object.create(HTMLElement.prototype);

SampleElementPrototype.createdCallback = function () {

  // &lt;sample-element&gt;にShadow Rootを生成する
  var shadowRoot = this.createShadowRoot();

  // &lt;style&gt;を生成する
  var style = document.createElement('style');
  var styleString = '';
  styleString += 'button {background: #000; color: #fff; font-size: 24px;}';
  styleString += 'input {font-size: 24px; background: #cfc;}';
  style.innerHTML = styleString;

  // &lt;input type='button'&gt;と&lt;button&gt;を生成する
  var input = document.createElement('input');
  input.setAttribute('type', 'text');
  var button = document.createElement('button');
  button.textContent = 'This is button.';

  // 生成した&lt;style&gt;と&lt;input type='button'&gt;と&lt;button&gt;をShadow Rootに追加する
  shadowRoot.appendChild(style);
  shadowRoot.appendChild(input);
  shadowRoot.appendChild(button);
};

// &lt;sample-element&gt;を定義する
document.registerElement('sample-element', {
  prototype: SampleElementPrototype
});</pre><p></p>

<p>作成したShadow Rootに対し、<code>style</code>・<code>input</code>・<code>button</code>の各要素を追加しました。Shadow Rootに要素が追加されるとShadow Hostの要素は表示されなくなり、代わりにShadow Rootの内容が表示されるようになります。</p>

<ul>
<li><a href="http://jsdo.it/1000ch/sample-element-1" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Sample Element &#8211; jsdo.it</a></li>
</ul>

<p>こちらは実際に動くサンプルです。</p>

<p><code>style</code>要素内には<code>button</code>要素を装飾するCSSを記述していますが、Shadow Root配下にあるので外部に漏れることはありません。また、グローバルな領域に<code>button</code>の装飾をするCSSがありますが、<code>sample-element</code>内の<code>button</code>に対しては影響していないことが確認できます。</p>

<p>このように、Shadow DOMはHTMLにスコープを提供します。Web Componentsに関連する機能の中でも、最も重要と言える機能かもしれません。</p>

<h2>Templates</h2>

<p>TemplatesはHTMLをひな形として扱うための仕様です。今までHTMLをひな形として扱いたい場合には<code>script</code>要素を使った方法等がありましたが、これらはあくまでハック的なアイデアに過ぎませんでした。Templatesでは<code>template</code>というタグで括ることで、ブラウザはその中のHTMLを不活性なHTML要素として認識します。不活性な要素は描画されることはなく、<code>document.querySelector()</code>等でアクセスすることもできません。</p>

<p>HTMLの生成はもちろんDOMのAPIでも可能ですが、HTML生成がJavaScript内で行われていると構造がわかりにくいですし、メンテナンスの観点からもHTML上にテンプレートが配置されているほうが望ましいです。</p>

<h3>template要素を利用する</h3>

<p>先程の、Shadow Rootに追加しているHTMLを<code>template</code>を使って書きなおしていきます。Shadow Rootに追加しているHTMLをそのまま<code>template</code>内に記述するだけです。この<code>template</code>要素にはIDを付与しておきます。</p>

<p></p><pre class="crayon-plain-tag">&lt;template id='sample-element-template'&gt;
  &lt;style&gt;
    button {
      background: #000;
      color: #fff;
      font-size: 24px;
    }
    input {
      font-size: 24px;
      background: #cfc;
    }
  &lt;/style&gt;
  &lt;input type='text'&gt;
  &lt;button&gt;Button&lt;/button&gt;
&lt;/template&gt;</pre><p></p>

<p>先程行っていた、 <strong>DOMのAPIでHTMLを生成しShadow Rootに追加する</strong> という処理を、 <strong>テンプレートをコピーしてShadow Rootに追加する</strong> という処理に置き換えます。</p>

<p></p><pre class="crayon-plain-tag">// &lt;sample-element&gt;のプロトタイプ
var SampleElementPrototype = Object.create(HTMLElement.prototype);

SampleElementPrototype.createdCallback = function () {

  // &lt;sample-element&gt;にShadow Rootを生成する
  var shadowRoot = this.createShadowRoot();

  // &lt;template&gt;を取得する
  var template = document.querySelector('#sample-element-template');
    
  // &lt;template&gt;の中の要素をコピーする
  var clone = document.importNode(template.content, true);

  // 生成した&lt;style&gt;と&lt;input type='button'&gt;と&lt;button&gt;をShadow Rootに追加する
  shadowRoot.appendChild(clone);
};

// &lt;sample-element&gt;を定義する
document.registerElement('sample-element', {
  prototype: SampleElementPrototype
});</pre><p></p>

<p>追加したい要素の構造がHTML側に整理されたことで、ぐっと見通しが良くなりました。</p>

<ul>
<li><a href="http://jsdo.it/1000ch/sample-element-2" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Sample Element with Templates &#8211; jsdo.it</a></li>
</ul>

<h2>HTML Imports</h2>

<p>Custom Elements、Shadow DOM、Templatesを使って<code>sample-element</code>を作成してきました。ここまでの処理をHTMLファイルにまとめて、そのHTMLをロードすることで<code>sample-element</code>が利用可能になります。</p>

<p>JavaScriptファイルや画像といったサブリソースをロードするには<code>script</code>や<code>img</code>要素を使った方法がありましたが、HTMLに関してはネイティブは存在していませんでした。<code>XMLHttpRequest</code>を使ってロードする方法もありますが、HTMLのロードに必ずJavaScriptを利用するのもやや大袈裟と言えます。この最も基本的とも言える <strong>外部のHTMLをロードする</strong> という機能を実現するのがHTML Importsです。</p>

<h3>HTML Importsを利用する</h3>

<p>外部のHTMLファイルをロードするには、以下のように<code>link</code>要素を使ってロードします。</p>

<p></p><pre class="crayon-plain-tag">&lt;link rel='import' href='sample-element.html'&gt;</pre><p></p>

<p>断片化されたHTMLファイルはこのように<code>link</code>要素をつかってロードすることが可能で、読み込まれたファイルは読み込み先のHTMLに引き継がれます。ここでは、Custom Elements、Shadow DOM、Templatesを使って構築してきた<code>sample-element</code>を外部ファイル化します。</p>

<p></p><pre class="crayon-plain-tag">&lt;template id='sample-element-template'&gt;
  &lt;style&gt;
    button {
      background: #000;
      color: #fff;
      font-size: 24px;
    }
    input {
      font-size: 24px;
      background: #cfc;
    }
  &lt;/style&gt;
  &lt;input type='text'&gt;
  &lt;button&gt;Button&lt;/button&gt;
&lt;/template&gt;

&lt;script&gt;
  // &lt;sample-element&gt;のプロトタイプ
  var SampleElementPrototype = Object.create(HTMLElement.prototype);

  SampleElementPrototype.createdCallback = function () {

    // &lt;sample-element&gt;にShadow Rootを生成する
    var shadowRoot = this.createShadowRoot();

    // &lt;template&gt;を取得する
    var template = document.querySelector('#sample-element-template');
    
    // &lt;template&gt;の中の要素をコピーする
    var clone = document.importNode(template.content, true);

    // 生成した&lt;style&gt;と&lt;input type='button'&gt;と&lt;button&gt;をShadow Rootに追加する
    shadowRoot.appendChild(clone);
  };

  // &lt;sample-element&gt;を定義する
  document.registerElement('sample-element', {
    prototype: SampleElementPrototype
  });
&lt;/script&gt;</pre><p></p>

<p>テンプレートとなるHTMLから、実際に<code>sample-element</code>を定義するスクリプト処理までをまとめて<code>sample-element.html</code>としました。
このように単一のHTMLファイルに集約することでコンポーネントの責任の在処も明確にすることが可能です。
外部ファイル化した<code>sample-element.html</code>をロードする最も単純な例は以下のようになるでしょう。以下を<code>index.html</code>とします。</p>

<p></p><pre class="crayon-plain-tag">&lt;html&gt;
  &lt;head&gt;
    &lt;link rel="import" href="sample-element.html"&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;sample-element&gt;&lt;/sample-element&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre><p></p>

<p>しかし、この<code>sample-element.html</code>をいざインポートしようとすると、エラーが発生します。具体的には<code>document.querySelector('#sample-element-template');</code>で要素が存在しないために<code>null</code>を返すためです。</p>

<h3>インポート時のdocumentの扱いに注意する</h3>

<p>HTMLの評価は<code>index.html</code>側で行われるため、<code>querySelector</code>の実行者である<code>document</code>はロード先の<code>index.html</code>になります。そうすると<code>sample-element.html</code>に配置してある<code>#sample-element-template</code>は<code>index.html</code>にないので、要素が見つからないという結果になってしまいます。</p>

<p>そのため、<code>querySelector</code>の実行者を<code>sample-element.html</code>の<code>document</code>にする必要がありますが、これには<code>document.currentScript</code>という属性を利用します。<code>document.currentScript</code>では実行中のスクリプトノードを返します。ノードからは<code>ownerDocument</code>を使って親となるドキュメントを参照することができるので、これらを組み合わせて<code>querySelector</code>の実行者が<code>sample-element.html</code>のドキュメントになるようにします。</p>

<ul>
<li><a href="https://html.spec.whatwg.org/multipage/dom.html#dom-document-currentscript" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">document.currentScript &#8211; HTML Standard</a></li>
<li><a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#node-ownerDoc" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Node.ownerDocument &#8211; Document Object Model Core</a></li>
</ul>

<p></p><pre class="crayon-plain-tag">&lt;script&gt;
  // 実行中のスクリプトを参照する
  var currentScript = document.currentScript;

  // &lt;sample-element&gt;のプロトタイプ
  var SampleElementPrototype = Object.create(HTMLElement.prototype);

  SampleElementPrototype.createdCallback = function () {

    // &lt;sample-element&gt;にShadow Rootを生成する
    var shadowRoot = this.createShadowRoot();

    // &lt;template&gt;を取得する
    var template = currentScript.ownerDocument.querySelector('#sample-element-template');
    
    // &lt;template&gt;の中の要素をコピーする
    var clone = document.importNode(template.content, true);

    // 生成した&lt;style&gt;と&lt;input type='button'&gt;と&lt;button&gt;をShadow Rootに追加する
    shadowRoot.appendChild(clone);
  };

  // &lt;sample-element&gt;を定義する
  document.registerElement('sample-element', {
    prototype: SampleElementPrototype
  });
&lt;/script&gt;</pre><p></p>

<p><code>querySelector</code>の実行者が<code>sample-element.html</code>の<code>document</code>になったことで正常に動くようになります。その直後の<code>document.importNode</code>や<code>document.registerElement</code>はそのままにしてあることにも注目してください。ノードのコピーや、カスタム要素の登録はインポート先のドキュメントで行うのが適切と言えるでしょう。これで晴れて<code>sample-element.html</code>のインポートが正常にできるようになりました。</p>

<ul>
<li><a href="http://jsdo.it/1000ch/sample-element-3" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Sample Element with HTML Imports &#8211; jsdo.it</a></li>
</ul>

<h2>まとめ</h2>

<p>Custom Elements、Shadow DOM、Templates、HTML Importsの4つの仕様の基本的な使い方について解説しました。簡単におさらいすると、以下のようになります。</p>

<ol>
<li>Custom Elementsでカスタム要素を新たに定義し、基本的な挙動を指定する。</li>
<li>カスタム要素に指定するCSSやJavaScriptの効力はShadow DOMに閉じ込める。</li>
<li>テンプレートとして扱うHTMLを<code>template</code>タグに宣言する。</li>
<li>カスタム要素を定義する一連の処理を記述したHTMLを、HTML Importsで読み込む。</li>
</ol>

<p>再利用可能なコンポーネント化を実現するために、それぞれがどういった役割を果たしているかを理解することはもちろん重要ですが、それらはあくまで独立した仕様であり、単一の機能として利用できることも認識しておきましょう。</p>

<p>次回は、より実践的なコンポーネント作成を解説する予定です。</p>
]]></content:encoded>
		
		<series:name><![CDATA[基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜]]></series:name>
	</item>
	</channel>
</rss>
