その他

Web Componentsでお気に入りボタンを作ってみましょう!

はじめに

最近、Onsen UIでWeb Components(ウェブ・コンポーネント)を使い始めました。このAPIで、ウェブ開発者は新しいHTMLのタグを楽に作れるようになります。Web Componentsを学びながら、下のようなシンプルな「お気に入りボタン」を作ってみましょう。

今回作成する「お気に入りボタン」は、カスタムエレメントなので作成後は簡単に利用できます。


<favorite-star></favorite-star>

クリックしたらはアニメーションでになります。

Onsen UIを作り始めた当時は、Web Componentsというものがないため、Onsen UIのカスタムエレメントはAngularのディレクティブで実装していましたが、今後はOnsen UIのコンポーネントをAngularJSだけではなく、どんなフレームワークでも利用可能にし、jQueryやReact.jsの開発者でも、Onsen UIでハイブリッドアプリを楽しく作れるようしたいと考えています。そのため、Onsen UIの中心部をWeb Componentsで実装することにしました。また、AngularJSのサポートもAngularJSのラッパーで継続して対応します。(Web Componentsは各ブラウザにはまだ実装されていないため、SafariやIEなどで対応するには、ポリフィルを使わないといけませんが、近い将来には必要なくなると思います。)

このチュートリアルのコードはここでダウンロードできます。

それでは始めましょう!

まずは、新しいタグを登録しないといけません。カスタムエレメントを登録するには新しくできたdocument.registerElement()という関数を使います。引数としては、「タグの名前」と「オプションオブジェクト」を入れます。


window.FavoriteStarElement = document.registerElement('favorite-star', {
  prototype: proto
});

prototypeというオプションでは、エレメントの動作を定義するのでとても大事です。

HTMLのタグを作っているので、プロトタイプはHTMLElement.prototypeををコピーして継承しましょう。


var proto = Object.create(HTMLElement.prototype);

<template>タグ

<template>タグの中には、カスタムエレメントで表示するお気に入りボタン(ここではUTF-8の★が入っている<span>タグ)とそれに適用するスタイルシートを入れます。


<template>
  <style>
    ...
  </style>
  <span class="favorite-star-character"> ★</span>
</template>

この記事ではスタイルシートについては触れませんが、興味がある方はこちらを参照してください。

シャドウDOMとは?

Web Componentsのスペックの一番紛らわしい部分は、多分シャドウDOMだと思います。シャドウDOMは標準DOMに付いている特別なサブツリーです。シャドウDOMに入っているスタイルシートはそのツリーのエレメントのみに影響を与えます。

これはカスタムエレメントを作る時には非常に便利です。エレメントの中に定義されているスタイルシートは副作用があるかどうか心配する必要がなくなりますね。

新しいシャドウDOMのサブツリーを作るにはdocument.createShadowRoot()を使います。

ChromeはもうポリフィルがなくてもシャドウDOMを対応していますので、DevToolsのインスペクターでシャドウDOMはこのように見えます。

Web Componentsのライフサイクルコールバック

カスタムエレメントのライフライクルには、特別なコールバックが四つあります。これらのコールバックで、エレメントの動作を制御することができます。コールバックはエレメントのプロトタイプオブジェクトに付けます。

一番大事なのはcreatedCallbackです。エレメントが生まれるときに起動される関数です。これを使うだけで、かなり複雑なカスタムエレメントがつくれます。


proto.createdCallback = function() {
  console.log('ハロー・ワールド');
};

これに加えて、attachedCallbackdetachedCallbackという関数もあります。attachedCallbackはエレメントがDOMに付くときに呼び出されます。detachedCallbackは逆にDOMから外されたときに呼びさだれます。

この二つの関数には、メモリーリークを起こさないようにイベントリスナーを登録したり、イベントリスナーを削除したりするととても便利です。

attributeChangedCallbackは、エレメント属性は変わった時に呼び出されます。

ここでは、まずお気に入りボタンのcreatedCallbackを作りましょう。


// documentオブジェクトを取ります。
var currentScript = document._currentScript || document.currentScript,
  doc = currentScript.ownerDocument,
// プロトタイプオブジェクトを作成します。
var proto = Object.create(HTMLElement.prototype);
// この関数はエレメントが生まれたときに呼び出されます。
proto.createdCallback = function() {
  // <template>タグのコンテンツを取ります。
  var template = doc.querySelector('template'),
      clone = document.importNode(template.content, true);
  // シャドウDOMのサブツリーを作成して、コンテンツを
  // 付けます。
  this.shadowRoot = this.createShadowRoot();
  this.shadowRoot.appendChild(clone);
  // 星の<span>タグを探します。
  this.element = this.shadowRoot.querySelector('.favorite-star-character');
  // イベントリスナー
  this.boundOnClick = this.onClick.bind(this);
  this.boundOnMouseover = this.onMouseover.bind(this);
  this.boundOnMouseout = this.onMouseout.bind(this);
  // 生まれたときにactiveの属性が付いてたら
  // <span>タグにactive属性を付けます。
  if (this.hasAttribute('active')) {
    this.element.setAttribute('active', '');
  }
};

ユーザーがクリックした場合とエレメントの上にホバーした場合の動作もつけないといけないので、
attachedCallbackでイベントリスナーを登録します。


proto.attachedCallback = function() {
  var el = this.element;
  // イベントリスナーを登録します。
  el.addEventListener('click', this.boundOnClick);
  el.addEventListener('mouseout', this.boundOnMouseout);
  el.addEventListener('mouseover', this.boundOnMouseover);
}
proto.detachedCallback = function() {
  var el = this.element;
  // イベントリスナーを削除します。
  el.removeEventListener('click', this.boundOnClick);
  el.removeEventListener('mouseout', this.boundOnMouseout);
  el.removeEventListener('mouseover', this.boundOnMouseover);
}
proto.toggle = function() {
  if (this.hasAttribute('active')) {
    this.removeAttribute('active');
  }
  else {
    this.setAttribute('active', '');
  }
}
proto.onClick = function() {
  this.toggle();
}
// この場合は:hoverクラスを使えないので、hoverというクラスを
// 作成します。:hoverを使ったら、金色の星をクリックしたら動作は
// 間違っています(灰色になりません)。
proto.onMouseover = function() {
  var el = this.element;
  if (!el.hasAttribute('active')) {
    el.setAttribute('hover', '');
  }
}
proto.onMouseout = function() {
  var el = this.element;
  el.removeAttribute('hover');
}

開発者の中には、element.setAttributeelement.removeAttributeで直接制御する可能性もありますので、それに対応するattributeChangedCallbackも作ります。


proto.attributeChangedCallback = function(attr) {
  if (attr === 'active') {
    var el = this.element;
    if (this.hasAttribute('active')) {
      el.setAttribute('active', '');
    }
    else {
      el.removeAttribute('active');
      el.removeAttribute('hover');
    }
  }
}

出来上がりです!

おわりに

まだこのAPIを対応していないブラウザはあるけど、ポリフィルを入れることで全てのブラウザに対応できるため、Web Componentsは既に実践に使える技術になったと感じますね。

私はちょっとしか触ってないけど、こんな風に新しいタグを作るのはとても便利なことだと思います。中身はどんなに複雑でも、外から見ればとても使いやすいHTMLタグにしか見えないです。

これからもいろんな便利なコンポーネントを作っていきましょう!

参考情報

前の記事へ

次の記事へ

一覧へ戻る

「その他」カテゴリの最新記事

PAGE TOP