Accelerated Mobile Pages(AMP)は、一貫して高パフォーマンスで高速表示される美しいウェブサイトと広告の作成を支援するオープンソース プロジェクトです。

AMP になじみのない方は、以下のページで概要を簡単に確認することができます。

目的

AMP は、イメージ カルーセルライトボックスなどの UI コンポーネントを利用したリッチな動的コンテンツをサポートしています。さらに AMP のアクションを使用して、あるコンポーネントから別のコンポーネントのアクションを簡単にトリガーする方法も複数備わっています。

しかし、次のような要件がある場合はどうでしょう。

従来は UI コンポーネント間の安定した通信チャネルがなく、状態の共有や変更が不可能であったため、上記のような機能を AMP で実装するのは困難でした。そこで、このようなユースケースに対応するために、強力な AMP コンポーネントを新たに導入しました。

<amp-bind>

<amp-bind> は、データ バインディングと JS のような式を用いて、独自のインタラクティブなページを作成するための新規 AMP コンポーネントです。このコードラボでは、<amp-bind> を使用して、独自のインタラクティブ性を備えたリッチな AMP ページを作成する方法を順に説明していきます。

作業内容

このコードラボでは、以下のように e コマースの商品詳細ページを作成します。

  • AMP HTML と AMP コンポーネントを使用して、高速でリッチなユーザー エクスペリエンスを実現する
  • <amp-bind> を使用して、要素間のインタラクティブ性を追加する
  • <amp-state> を使用して、必要時に商品データを追加で取得する

学習内容

必要なもの

コードのダウンロード

まず、このコードラボで使うスターターコードを ZIP ファイル形式でダウンロードします。

ダウンロード

git からもダウンロードできます。

git clone https://github.com/googlecodelabs/advanced-interactivity-in-amp.git

依存パッケージのインストール

(必要に応じて)アーカイブ ファイルを解凍して、そのディレクトリに移動します。npm install コマンドを実行して依存パッケージをインストールします。

cd advanced-interactivity-in-amp-codelab
npm install

開発サーバーの起動

node.js で開発サーバーを起動します。

node app.js

ブラウザで http://localhost:3000 を開くと AMP ページが表示されます。

AMP ボイラープレート

AMP ページは、パフォーマンスを保証するための制約が設けられた HTML ページです。AMP ページでは、Google 検索時にそのページが AMP として識別されるように特殊なマークアップをいくつか使用しています。

以下は AMP ページの基本構成です。

<!doctype html>
<html amp>
 <head>
   <meta charset="utf-8">
   <link rel="canonical" href="hello-world.html">
   <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
   <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
   <script async src="https://cdn.ampproject.org/v0.js"></script>
 </head>
 <body>Hello World!</body>
</html>

AMP コンポーネント

スターター コード(static/index.html)は、上記の基盤となる AMP ページにページコンテンツ(画像、テキストなど)と AMP コンポーネントをいくつか組み込んで作成したものです。

<script async custom-element="amp-carousel" src="https://cdn.ampproject.org/v0/amp-carousel-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.1.js"></script>
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
<script async custom-element="amp-selector" src="https://cdn.ampproject.org/v0/amp-selector-0.1.js"></script>

AMP コンポーネントを使用して機能や UI コンポーネントを追加することによって、よりリッチでインタラクティブな AMP ページを作成できます。このスターター コードで使用している AMP コンポーネントは次のとおりです。

基本的なインタラクティブ処理

スターター コードでは、次の基本的なインタラクティブ操作が可能です。

イメージ カルーセルをスワイプして [Add to cart] ボタンをタップしてみてください。

ユーザー エクスペリエンスの改善

先ほど紹介したスターター コードで実現できるのは、ごく基本的なユーザー エクスペリエンスのみです。よりよいページに改良するには、次のような方法があります。

このような機能は、新たに導入された <amp-bind> コンポーネントによって追加可能になりました。では、実際に <amp-bind> を使用して、これらの新機能をサンプルコードに追加していきましょう。

<amp-bind> 拡張機能のインストール

<amp-bind> は、データ バインディングと JS のような式を用いて独自のインタラクティブなページを作成するための新規 AMP コンポーネントです。<amp-bind> を使用するには、まずこのコンポーネントをページにインストールする必要があります。

static/index.html ファイルを開いて、ページの <head> セクションに含まれる AMP コンポーネントの一覧に次のスクリプトを追加します。

<script async custom-element="amp-bind"
    src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>

スライド インジケーターの追加

<amp-bind> は、要素属性をカスタム式にバインドすることで機能します。このような式は「状態」(変更可能な JSONデータ)を参照することが可能で、この状態は <amp-bind> に含まれる <amp-state> コンポーネントで初期化できます。

では、イメージ カルーセルで表示中のスライド インデックスをトラックするために、状態変数を初期化しましょう。static/index.html を開いて、以下の内容をページの <body> の先頭(ヘッダーの前)に追加します。

<amp-state id="selected">
  <script type="application/json">
    {
      "slide":0
    }
  </script>
</amp-state>

<amp-state> 要素内のデータにアクセスするには、それらに紐づけられた ID を使用します。たとえば、この変数は次のような式のフラグメントによって参照できます。

selected.slide // Evaluates to 0.

次は、ユーザーがカルーセル上でスライドを変更したときに、この変数を更新するために既存の <amp-carousel> 要素に "on" アクションを追加しましょう。

<amp-carousel type="slides" layout="fixed-height" height=250 id="carousel"
    on="slideChange:AMP.setState({selected: {slide: event.index}})">

これで、<amp-carousel> で表示中のスライドが変化するたびに、以下の引数を指定して AMP.setState アクションが呼び出されるようになりました。

{
  selected: {
    slide: event.index
  }
}

event.index 式の評価結果は新しいスライド インデックスになり、このオジェクト リテラルが AMP.setState() アクションによって現在の状態にマージされます。その結果、現在の selected.slide の値が event.index に置き換わります。

次は現在表示中のスライドをトラックする状態変数を利用して、スライド インジケーターを作成しましょう。スライド インジケーターの要素を探して(<!-- TODO: "Add a slide indicator" --> を検索)、その子要素に以下のバインディングを追加します。

<!-- TODO:"Add a slide indicator" -->
<p class="dots">
  <!-- The <span> element corresponding to the current displayed slide
       will have the 'current' CSS class. -->
  <span [class]="selected.slide == 0 ? 'current' : ''" class="current"></span>
  <span [class]="selected.slide == 1 ? 'current' : ''"></span>
  <span [class]="selected.slide == 2 ? 'current' : ''"></span>
</p>

[class]class 属性を変更するためのバインディングで、これを使用すると CSS クラスを任意の要素に対して追加または削除できます。

では、ページを更新して表示を確認してください。カルーセル上のスライドを変更すると、次の流れで処理が実行されます。

  1. slideChange イベントのトリガー
  2. AMP.setState アクションの呼び出し
  3. 状態変数 selected.slide の更新
  4. インジケーターの <span> 要素で [class] バインディングの更新

これでスライド インジケーターが機能するようになりました。

<amp-carousel> での画像変更

ユーザーが違う色を選択したときには、違う色のシャツの画像が表示されるのが理想的です。これを実現するには amp-bind を使用して、[src]<amp-carousel> 内の <amp-img> 要素にバインドします。

まずは、状態データを各色のシャツの画像ソース URL で初期化する必要があります。この処理は、次のように新しい <amp-state> 要素で行います。

<!-- Available shirts.Maps unique string identifier to color and image URL string. -->
<amp-state id="shirts">
  <script type="application/json">
    {
      "1001": {
        "color": "black",
        "image": "./shirts/black.jpg"
      },
      "1002": {
        "color": "blue",
        "image": "./shirts/blue.jpg"
      },
      "1010": {
        "color": "brown",
        "image": "./shirts/brown.jpg"
      },
      "1014": {
        "color": "dark green",
        "image": "./shirts/dark-green.jpg"
      },
      "1015": {
        "color": "gray",
        "image": "./shirts/gray.jpg"
      },
      "1016": {
        "color": "light gray",
        "image": "./shirts/light-gray.jpg"
      },
      "1021": {
        "color": "navy",
        "image": "./shirts/navy.jpg"
      },
      "1030": {
        "color": "wine",
        "image": "./shirts/wine.jpg"
      }
    }
  </script>
</amp-state>

この <amp-state> 要素に含まれる JSON オブジェクトは、シャツの識別文字列(SKU など)をその色と対応するシャツの画像の URL にマッピングします。ここで JSON 配列を使うことも可能ですが、以下で示すようにオブジェクトを使った方がより便利です。

これで、シャツの識別子によって画像の URL にアクセスできるようになりました。たとえば、shirts['10014'].color の評価結果は "dark green" になり、shirts['10030'].image は "wine" 色のシャツの画像の URL を返します。

選択中の SKU をトラックする別の状態変数を追加すると、式を <amp-img> 要素にバインドして、選択中の SKU が変化したときに対応する src 属性を更新することができます。次のように、新しい sku キーを既存の amp-state#selected 要素の JSON に追加します。

<amp-state id="selected">
  <script type="application/json">
    {
      "slide":0,
      "sku":"1001"
    }
  </script>
</amp-state>

さらに、"on" アクションを <amp-selector> に追加して、新しい色が選択されるたびに selected.sku 変数を更新します。

<amp-selector name="color" 
    on="select:AMP.setState({selected: {sku: event.targetOption}})">

次に、<amp-carousel> 内の <amp-img> 要素にバインディングを追加します。(<!-- TODO: "Changing images in amp-carousel-->" を検索)

<!-- Update the `src` of each <amp-img> when the `selected.sku` variable changes. -->
<amp-img width=200 height=250 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=300 height=375 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=400 height=500 src="./shirts/black.jpg"
    [src]="shirts[selected.sku].image"></amp-img>

注: 実際には、カルーセルの各画像に対して異なる src が存在するのが一般的です。その場合は、1 つの画像の代わりに画像配列を使用します。このコードラボでは簡略化のために、1 つの画像を倍率を変えて使用しています。

では、ページを更新して別のシャツの色を選択してください。カルーセルの画像が更新されて、選択した色のシャツが表示されるはずです。

バインド可能なデータがあまりにも多い、または複雑すぎて、ページの読み込み時に取得できない場合はどうしたらよいでしょうか。また、各 SKU の価格確認に時間がかかりすぎる場合はどうでしょう。ユーザーに表示されない SKU の価格を調べるのは無駄な処理です。

購入可能なシャツのサイズを取得

リモートデータを取得する機能を利用して、コードラボのサンプルで SKU の価格を調べましょう。app.js にある Express.js 開発サーバーには、すでにエンドポイント /shirts/sizes?shirt=<sku> があります。これは指定されたシャツの SKU に応じて、購入可能なサイズと各サイズの価格を返します。ネットワーク遅延を再現するために、レスポンスは人工的に 1 秒遅らせています。

リクエスト

レスポンス

GET /shirts/sizesAndPrices?sku=1001

{"1001: {"sizes": {"XS":8.99, "S" 9.99}}}

<amp-state> 要素内の JSON データと同様に、このように取得したリモートデータはマージされ、要素の id 属性を介して利用可能になります。たとえば上の例のレスポンスで返されたデータには、以下の式でアクセス可能です。

結果

shirts['1001'].sizes['XS']

8.99

では、これを e コマースの例に適用しましょう。まず、新しい SKU が選択されたときにシャツのデータを取得します。次のように [src] バインディングを amp-state#shirts 要素に追加します。

<!-- When `selected.sku` changes, update the `src` attribute and fetch
     JSON at the new URL.Then, merge that data under `id` ("shirts"). -->
<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">

次は、指定された SKU において購入不可能なサイズを明示します。以下のように amp-selector[name="size"] 内で、購入不可能なサイズに対応する要素に "unavailable" CSS クラスを追加すると、その要素に斜線が引かれます。

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

では、ページを再読み込みして表示を確認してください。新しい SKU(シャツの色)を選択すると、(少し遅れて)購入不可能なサイズに斜線が表示されるはずです。

ただし、まだ小さな問題が残っています。デフォルトで選択されている黒のシャツの扱いです。黒のシャツのサイズと価格のデータについては、次のように amp-state#shirts に追加する必要があります。

<amp-state id="shirts" [src]="'/shirts/sizesAndPrices?sku=' + selected.sku">
  <script type="application/json">
    {
      "1001": {
        "color": "black",
        "image": "./shirts/black.jpg",
        "sizes": {
          "XS":8.99,
          "S":9.99
        }
      },
<!-- ... -->

関連要素の初期状態も更新します。

<amp-selector name="size">
  <table>
    <tr>
      <!-- If 'XS' size is available for selected SKU, return empty string.
           Otherwise, return 'unavailable'. -->
      <td [class]="shirts[selected.sku].sizes['XS'] ? '' : 'unavailable'">
        <div option="XS">XS</div>
      </td>
      <td [class]="shirts[selected.sku].sizes['S'] ? '' : 'unavailable'">
        <div option="S">S</div>
      </td>
      <!-- Add the ‘unavailable' class to the next three <td> elements
           to be consistent with the available sizes of the default SKU. -->
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['M'] ? '' : 'unavailable'">
        <div option="M">M</div>
      </td>
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['L'] ? '' : 'unavailable'">
        <div option="L">L</div>
      </td>
      <td class="unavailable" 
          [class]="shirts[selected.sku].sizes['XL'] ? '' : 'unavailable'">
        <div option="XL">XL</div>
      </td>
    </tr>
  </table>
</amp-selector>

シャツの表示価格の切り替え

購入可能なサイズを正確に表示できるようになったので、次は適切な価格も表示されるようにしましょう。

この AMPPAREL ストアでは、シャツの価格は色とサイズの両方によって決まります。つまりユーザーが選択したサイズをトラックするための変数が新たに必要です。次のように、サイズの <amp-selector> 要素に新しいアクションを追加しましょう。

<!-- When an element is selected, set the `selectedSize` variable to the
     value of the "option" attribute of the selected element.  -->
<amp-selector name="size" 
    on="select:AMP.setState({selectedSize: event.targetOption})">

ここで、amp-state#selected 要素で selectedSize の値を初期化していない点に注目してください。ユーザーに必ずサイズを選択させるために、あえてデフォルトのサイズを未指定にしています。

新しい <span> 要素を追加して価格ラベルをラップします。また、デフォルトのサイズは選択されていないので、デフォルトのテキストを「---」に変更します。

<h6>PRICE :
  <!-- Display the price of the selected shirt in the selected size if available.
       Otherwise, display the placeholder text '---'. -->
  <span [text]="shirts[selected.sku].sizes[selectedSize] || '---'">---</span>
</h6>

これで適切な価格が表示されるようになりました。実際に確認してみてください。

条件に応じて有効になるボタン

完成まであともう少しです。次は、購入不可能なサイズが選択されたときに [Add to cart] ボタンを非表示にします。

<!-- Disable the "ADD TO CART" button when:
     1.There is no selected size, OR
     2.The available sizes for the selected SKU haven't been fetched yet
-->
<input type="submit" value="ADD TO CART" disabled
    class="mdl-button mdl-button--raised mdl-button--accent"
    [disabled]="!selectedSize || !shirts[selected.sku].sizes[selectedSize]">

これで、e コマースの商品詳細ページが完成しました。表示されるサイズと価格は必要時に JSON エンドポイントから取得したもので、各 SKU に応じて変化します。

途中でわからなくなった場合は、完成形を static/final.html でご確認ください。

このコードラボを通じて、<amp-bind> を使うと、効果的かつ柔軟にインタラクティブな AMP ページを作成できることをご理解いただければ幸いです。詳細については、<amp-bind> のドキュメントをご覧ください。

機能に関するリクエストや提案、バグの報告などがありましたら、ぜひフィードバックをお寄せください。また、実際のユーザーを対象に <amp-bind> をテストすることに興味がある方は、オリジン トライアルへの参加をご検討ください。

間近に迫った <amp-bind> の公開と、このコンポーネントを使用してデベロッパーの皆様が作成したすばらしい AMP ページを見ることを楽しみにしております。