泉水翔吾

Web Componentsでカルーセルギャラリーを作る─Web Components実践編

この記事は、連載「基礎からわかる Web Components 徹底解説 〜仕様から実装まで理解する〜」の第2回目になります。

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

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

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

カルーセルギャラリーを構成するためのjQueryのプラグインなどは多数配布されていますが、サードパーティのリソースを導入するリスクについては前回の記事で触れたとおりです。今回は実践編として、これをコンポーネント化し、タグのみでカルーセルを実現する<carousel-panel>という要素を実際に作っていきます。

carousel-panel

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

仕様の決定と利用のイメージ

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

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

以下は配置するHTMLのイメージです。

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

カルーセルのベースとなる要素を作成する(Web Componentsの基本手順のおさらい)

では、前回の記事で解説した内容を踏まえて、次のような流れで作成していきます。

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

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

カルーセル要素へ挿入したコンテンツを利用する(Shadow DOMへのコンテンツ挿入)

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

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

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

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

content

<content>による挿入ポイントを複数定義し、参照をコントロールする手段も存在します。これらのShadow DOMのさらなるコンセプトについては、HTML5RocksのShadow DOM 301 – 上級者向けコンセプトと DOM APIという記事を参照してください。

カルーセル要素のスタイリング(Shadow DOMへ追加したコンテンツのスタイリング)

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

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

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

Shadow DOMに関連するCSSセレクタ

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

  • :host Shadow Treeをホストしている要素(つまり、カスタム要素)を指すセレクタ
  • ::content <content>による挿入ポイントをShadow DOM内部から参照するセレクタ

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

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

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

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

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

これらのShadow DOMに関するCSSセレクタについての詳細は、HTML5RocksのShadow DOM 201 – CSS とスタイリングが参考になるでしょう。

Shadow DOM配下の<link>要素は無効になる

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

結論から言えば、Shadow DOM配下の<link>要素は無効です。<link>の他にも、<base>要素がShadow DOM配下では不活性でなければならないと、仕様で定められています。これについての詳細は7.1 Inert HTML Elements – Shadow DOMを見てください。

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

カルーセル要素のJavaScriptによる挙動の追加(ライフサイクルコールバックの利用)

attachedCallback

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

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

今回のカルーセルの実装では、画像のスワイプ時の実装等をこのattachedCallback内で実装しました。Web Components特有の実装というものはなく、DOMのAPIを使った実装が続くので、記事での解説は割愛します。なお、最後にも紹介しますが今回の<carousel-panel autoslide>はGitHubにて全てのソースコードが公開されています。attachedCallbackの実装はこちらです。

detachedCallback

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

attributeChangedCallback

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

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

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

カルーセル要素の完成(デモとソースコード)

ここまでの解説を踏まえて、後は実際にJavaScriptでスワイプ時の動作などをコールバックを利用して定義するだけです。実際に作成した<carousel-panel>はこちらです。JavaScriptのコードは少し長いので、GitHubのリポジトリから確認していただきたいと思います。

demo

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

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

まとめ

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

次回は、Web Componentsをより柔軟に、そして強力に利用出来るようにするPolymerというライブラリについて紹介します。

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Canvas Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js PhoneGap Polymer SkyWay spdy TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket