AMP

amp-bind

データ バインディングと式を使用して独自のインタラクティブ性を追加します。

必要なスクリプト
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
チュートリアル インタラクティブな AMP ページを作成する

概要

amp-bind コンポーネントを使用すると、データ バインディングと JS に似た式を使って独自のステートフルなインタラクティブ性を AMP ページに追加できます。

この動画で amp-bind の概要をご覧ください。

簡単な例

次の例では、ボタンをタップすると、<p> 要素のテキストが「Hello World」から「Hello amp-bind」に変わります。

<p [text]="'Hello ' + foo">Hello World</p>

<button on="tap:AMP.setState({foo: 'amp-bind'})">Say "Hello amp-bind"</button>

amp-bind では、パフォーマンスの向上と、予期しないコンテンツの移動が発生するリスクの回避のために、ページの読み込み時に式の評価を行いません。そのため、表示要素のデフォルトの状態を指定して、最初のレンダリングでamp-bindを使用しないようにしてください。

仕組み

amp-bind には以下の 3 つの主要なコンポーネントがあります。

  1. 状態 : ドキュメント スコープの変更可能な JSON ステータス。上の例では、ボタンをタップする前の状態は空です。ボタンをタップした後の状態は {foo: 'amp-bind'}です。
  2. : 状態を参照可能な、JavaScript に似た式です。上の例の式'Hello ' + fooは、文字列リテラル 'Hello' とステータス変数 foo を連結します。式で使用できるオペランドには 100 個の制限があります。
  3. バインディング: : 要素のプロパティを式にリンクする、[プロパティ] の形式の特別な属性です。上の例のバインディング [text] は、式の値が変化するたびに要素のテキストを更新します。

amp-bind `では、AMP ページの速度、セキュリティ、パフォーマンスを確保するために特別な措置が取られています。

少し複雑な例

<!-- Store complex nested JSON data in <amp-state> elements. -->
<amp-state id="myAnimals">
  <script type="application/json">
    {
      "dog": {
        "imageUrl": "/img/dog.jpg",
        "style": "greenBackground"
      },
      "cat": {
        "imageUrl": "/img/cat.jpg",
        "style": "redBackground"
      }
    }
  </script>
</amp-state>

<p [text]="'This is a ' + currentAnimal + '.'">This is a dog.</p>

<!-- CSS classes can also be added or removed with [class]. -->
<p class="greenBackground" [class]="myAnimals[currentAnimal].style">
  Each animal has a different background color.
</p>

<!-- Or change an image's src with the [src] binding. -->
<amp-img width="300" height="200" src="/img/dog.jpg" [src]="myAnimals[currentAnimal].imageUrl">
</amp-img>

<p><button on="tap:AMP.setState({currentAnimal: 'cat'})">Set to Cat</button>

ボタンを押すと、次のようになります。

  1. 状態が更新されます(currentAnimal がd 'cat' に定義されます)。
  2. currentAnimal に依存するが評価されます。

    • 'This is a ' + currentAnimal + '.' => 'This is a cat.'
    • myAnimals[currentAnimal].style => 'redBackground'
    • myAnimals[currentAnimal].imageUrl => /img/cat.jpg
  3. 変更された式に依存するバインディングが更新されます。

    • 1 つ目の <p> 要素のテキストが「This is a cat.」になります。
    • 2 つ目の <p> 要素の class 属性が「redBackground」になります。
    • amp-img 要素によって猫の画像が表示されます。

この例のライブデモをお試しください。コードの注釈もご覧いただけます。

詳細

状態

amp-bind を使用する各 AMP ドキュメントでは、ドキュメント スコープの変更可能な JSON データ(状態)が作成されます。

amp-state による状態の初期化

amp-bind の状態は、amp-state コンポーネントを通じて初期化できます。

<amp-state id="myState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state>

では、ドット構文によってステータス変数を参照できます。この例の myState.foo"bar" として評価されます。

  • <amp-state> 要素の子 JSON の最大サイズは 100 KB です。
  • <amp-state> 要素では、子 JSON スクリプトの代わりに CORS URL を指定することもできます。詳しくは、付録をご覧ください。

状態の更新

状態コンポーネントでは refresh アクションがサポートされており、このアクションを使用して状態の内容を更新することができます。

<amp-state id="amp-state" ...></amp-state>
<!-- Clicking the button will refresh and refetch the json in amp-state. -->
<button on="tap:amp-state.refresh"></button>

AMP.setState() による状態の更新

AMP.setState() アクションは、オブジェクト リテラルを状態にマージします。たとえば、下のボタンを押すと、AMP.setState() によってオブジェクト リテラルが状態にディープマージされます。

<!-- Like JavaScript, you can reference existing
      variables in the values of the  object literal. -->
<button on="tap:AMP.setState({foo: 'bar', baz: myAmpState.someVariable})"></button>

一般に、ネストされたオブジェクトをマージする場合の最大深度は 10 です。変数(amp-state によって導入された変数を含む)はすべてオーバーライド可能です。

AMP.setState() は、特定のイベントによってトリガーされた場合、event プロパティでイベント関連のデータにアクセスすることもできます。

<!-- The "change" event of this <input> element contains
      a "value" variable that can be referenced via "event.value". -->
  <input type="range" on="change:AMP.setState({myRangeValue: event.value})">

AMP.pushState() による履歴の変更

AMP.pushState() アクションは、新しいエントリもブラウザの履歴スタックにプッシュすることを除き、AMP.setState() に似ています。この履歴エントリを(戻るなどの操作によって)ポップすると、AMP.pushState() で設定された以前の変数の値が復元されます。

例:

<button on="tap:AMP.pushState({foo: '123'})">Set 'foo' to 123</button>
  • ボタンをタップすると、変数 foo が 123 に設定され、新しい履歴エントリがプッシュされます。
  • 戻る操作を行うと、foo が以前の値 "bar" に復元されます(AMP.setState({foo: 'bar'}) を呼び出すのと同じ効果があります)。

式は JavaScript に似ていますが、重要な違いがいくつかあります。

JavaScript との違い

  • 式は、含んでいるドキュメントの状態にのみアクセスできます。
  • 式は、windowdocument などのグローバル変数にはアクセスできません
  • ホワイトリストに登録されている関数と演算子のみを使用できます。
  • 一般に、独自の関数、クラス、ループは使用できません。arrow 関数はパラメータとして使用できます(例: Array.prototype.map)。
  • 未定義の変数と array-index-out-of-bounds は、undefined を返したりエラーをスローしたりするのではなく、null を返します。
  • パフォーマンスの向上のために、現在は 1 つの式で使用できるオペランドの数が 50 個に制限されています。不十分な場合はお問い合わせください

式の文法と実装について詳しくは、bind-expr-impl.jisonbind-expression.js をご覧ください。

以下はすべて有効な式です。

1 + '1'           // 11
1 + (+'1')        // 2
!0                // true
null || 'default' // 'default'

ホワイトリストに登録されている関数

オブジェクトの種類 関数
配列1 concat
filter
includes
indexOf
join
lastIndexOf
map
reduce
slice
some
sort(not-in-place)
splice(not-in-place)
// Returns [1, 2, 3].
[3, 2, 1].sort()
// Returns [1, 3, 5].
[1, 2, 3].map((x, i) => x + i)
// Returns 6.
[1, 2, 3].reduce((x, y) => x + y)
数値 toExponential
toFixed
toPrecision
toString
// Returns 3.
                (3.14).toFixed()
// Returns '3.14'.
                  (3.14).toString()
文字列 charAt
charCodeAt
concat
indexOf
lastIndexOf
slice
split
substr
substring
toLowerCase
toUpperCase
// Returns 'abcdef'.
                      abc'.concat('def')
計算2 abs
ceil
floor
max
min
random
round
sign
// Returns 1.
                          abs(-1)
オブジェクト2 keys
values
// Returns ['a', 'b'].
                            keys({a: 1, b: 2})
// Returns [1, 2].
                              values({a: 1, b: 2}
グローバル2 encodeURI
encodeURIComponent
// Returns 'Hello%20world'.
                                encodeURIComponent('Hello world')

1 パラメータが 1 つだけの arrow 関数では、かっこは使用できません(例: (x) => x + 1 ではなく、x => x + 1 を使用します)。また、sort()splice() は、in-place を操作するのではなく、変更されたコピーを返します。 2 静的関数は名前空間化されません(例: Math.abs(-1) ではなく、abs(-1) を使用します)。

amp-bind-macro によるマクロの定義

amp-bind-macro を定義することにより、amp-bind の式フラグメントを再利用できます。amp-bind-macro 要素を使用すると、0 個以上の引数を取り、現在の状態を参照する式を定義できます。マクロは、ドキュメント内の任意の場所から id 属性の値を参照することで、関数のように呼び出すことができます。

<amp-bind-macro id="circleArea" arguments="radius" expression="3.14 * radius * radius"></amp-bind-macro>

<div>
  The circle has an area of <span [text]="circleArea(myCircle.radius)">0</span>.
</div>

マクロは、自身より前に定義された他のマクロを呼び出すこともできます。自身を再帰的に呼び出すことはできません。

バインディング

バインディングは、要素のプロパティをにリンクする、[property] の形式の特別な属性です。代わりに、XML 互換の構文を data-amp-bind-property の形式で使用することもできます。

状態 が変わると式が再評価され、バインドされた要素のプロパティが新しい式の結果で更新されます。

amp-bind は、以下の 4 種類の要素の状態に基づいてデータ バインディングをサポートします

種類 属性 詳細
Node.textContent [text] ほとんどのテキスト要素でサポートされています。
CSS クラス [class] 式の結果がスペース区切りの文字列である必要があります。
hidden 属性 [hidden] ブール式である必要があります。
AMP 要素のサイズ [width]
[height]
AMP 要素の幅または高さ(あるいはその両方)を変更します。
要素固有の属性 各種

バインディングに関する注意事項:

  • セキュリティ上の理由から、innerHTML へのバインドは禁止されています。
  • 属性のバインディングは、安全でない値(javascript: など)の場合はすべてサニタイズされます。
  • ブール式の結果によってブール値の属性が切り替えられます。たとえば、<amp-video [controls]="expr"...> では、expr の評価結果が true の場合、<amp-video> 要素に controls 属性が設定されます。expr の評価結果が false の場合、controls 属性が削除されます。
  • DOM API で XML(XHTML、JSX など)や属性を書き込む場合に、属性名の角かっこ([])が問題になることがあります。このような場合は、構文に [x]="foo" ではなく data-amp-bind-x="foo" を使用します。

要素固有の属性

以下のコンポーネントと属性へのバインディングのみが許可されています。

コンポーネント 属性 動作
<amp-brightcove> [data-account]
[data-embed]
[data-player]
[data-player-id]
[data-playlist-id]
[data-video-id]
表示される Brightcove 動画を変更します。
<amp-carousel type=slides> [slide]* 現在表示されているスライドのインデックスを変更します。例をご覧ください
<amp-date-picker> [min]
[max]
選択可能な最も古い日付を設定します。
選択可能な最も新しい日付を設定します。
<amp-google-document-embed> [src]
[title]
更新された URL のドキュメントを表示します。
ドキュメントのタイトルを変更します。
<amp-iframe> [src] iframe のソース URL を変更します。
<amp-img> [alt]
[attribution]
[src]
[srcset]
[src] にバインドする場合、キャッシュ上でバインドを行うには、[srcset] にもバインドします。
対応する amp-img 属性をご確認ください。
<amp-lightbox> [open]* ライトボックスの表示を切り替えます。ヒント: ライトボックスが閉じているときに変数を更新するには、on="lightboxClose: AMP.setState(...)" を使用します。
<amp-list> [src] 式が文字列の場合は、JSON を文字列の URL から取得してレンダリングします。式がオブジェクトまたは配列の場合は、式データをレンダリングします。
<amp-selector> [selected]*
[disabled]
現在選択されている子要素を変更します。
子要素は option 属性の値で特定できます。値のカンマ区切りのリストから複数の値を選択できます。例をご覧ください
<amp-state> [src] JSON を新しい URL から取得して既存の状態にマージします。次の更新では、サイクルの防止のために <amp-state> 要素が無視されます。
<amp-video> [alt]
[attribution]
[controls]
[loop]
[poster]
[preload]
[src]
対応する amp-video 属性をご確認ください。
<amp-youtube> [data-videoid] 表示される YouTube 動画を変更します。
<a> [href] リンクを変更します。
<button> [disabled]
[type]
[value]
対応する button 属性をご確認ください。
<details> [open] 対応する details 属性をご確認ください。
<fieldset> [disabled] フィールドセットを有効または無効にします。
<image> [xlink:href]
対応する image 属性をご確認ください。
<input> [accept]
[accessKey]
[autocomplete]
[checked]
[disabled]
[height]
[inputmode]
[max]
[maxlength]
[min]
[minlength]
[multiple]
[pattern]
[placeholder]
[readonly]
[required]
[selectiondirection]
[size]
[spellcheck]
[step]
[type]
[value]
[width]
対応する input 属性をご確認ください。
<option> [disabled]
[label]
[selected]
[value]
対応する option 属性をご確認ください。
<optgroup> [disabled]
[label]
対応する optgroup 属性をご確認ください。
<select> [autofocus]
[disabled]
[multiple]
[required]
[size]
対応する select 属性をご確認ください。
<source> [src]
[type]
対応する source 属性をご確認ください。
<track> [label]
[src]
[srclang]
対応する track 属性をご確認ください。
<textarea> [autocomplete]
[autofocus]
[cols]
[disabled]
[maxlength]
[minlength]
[placeholder]
[readonly]
[required]
[rows]
[selectiondirection]
[selectionend]
[selectionstart]
[spellcheck]
[wrap]
対応する textarea 属性をご確認ください。

* バインド可能な属性を示します(同等のバインド不可能な属性はありません)。

デバッグ

開発中に警告とエラーに焦点を当てたり、特殊なデバッグ関数を使用したりするには、開発モードで(URL フラグメント #development=1 を使用して)テストを実施します。

警告

開発モードでは、バインドされた属性のデフォルト値が対応する式の初期結果と一致しない場合、amp-bind によって警告が発行されます。これにより、他の状態変数の変化に伴って生じる意図しない変化を防止できます。以下に例を示します。

<!-- The element's default class value ('def') doesn't match the expression result for [class] ('abc'),
so a warning will be issued in development mode. -->

<p class="def" [class]="'abc'"></p>

開発モードでは、未定義の変数またはプロパティの参照を解除した場合にも、amp-bind によって警告が発行されます。これにより、null 式の結果が原因の意図しない変化も防止できます。以下に例を示します。

<amp-state id="myAmpState">
  <script type="application/json">
    { "foo": 123 }
</script>
</amp-state></p>

<!-- The amp-state#myAmpState does not have a `bar` variable, so a warning
  will be issued in development mode. -->
<p [text]="myAmpState.bar">Some placeholder text.</p>

エラー

amp-bind の使用時に発生する可能性があるランタイム エラーにはさまざまな種類があります。

種類 メッセージ アドバイス
無効なバインディング <P> 要素の [someBogusAttribute] にはバインドできません ホワイトリストに登録されているバインディングのみを使用してください。
構文エラー 式のコンパイル エラーです。 式に入力ミスがないか確認してください。
ホワイトリストに登録されていない関数 alert はサポート対象の関数ではありません。 ホワイトリストに登録されている関数のみを使用してください。
サニタイズされた結果 「javascript:alert(1)」は [href] の有効な結果ではありません。 禁止されている URL プロトコルや式を使用しないでください。AMP 検証ツールでエラーが発生します。
CSP 違反 次のコンテンツ セキュリティ ポリシーのディレクティブに違反しているため、「blob:...」からのワーカーの作成を拒否されました。 default-src blob: をオリジンのコンテンツ セキュリティ ポリシーに追加してください。コストのかかる作業が amp-bind によって専用のウェブワーカーに委任されるため、優れたパフォーマンスを実現できます。

状態のデバッグ

AMP.printState() を使用して、現在の状態をコンソールに出力します。

付録

<amp-state> の仕様

amp-state 要素には、子 <script> 要素、またはリモートの JSON エンドポイントの CORS URL を含む src 属性のいずれかを含めることができます。両方を含めることはできません。

<amp-state id="myLocalState">
  <script type="application/json">
    {
      "foo": "bar"
      }
  </script>
</amp-state></p>

<p><amp-state id="myRemoteState" src="https://data.com/articles.json">
</amp-state>

XHR のバッチ処理

AMP では、JSON エンドポイントに対する XMLHttpRequest(XHR)をバッチ処理します。つまり、AMP ページでは、複数のコンシューマー(複数の amp-state 要素など)のデータソースとして単一の JSON データ リクエストを使用できます。たとえば、amp-state 要素によってエンドポイントへの XHR が作成された場合、XHR の送信中は、同じエンドポイントに対する後続の XHR はトリガーされず、代わりに 1 つ目の XHR の結果を返します。

属性

src amp-state を更新する JSON を返すリモート エンドポイントの URL。この属性には CORS HTTP サービスを指定する必要があります。 src 属性では、標準的な URL 変数の置換をすべて使用できます。詳しくは、置換ガイドをご覧ください。

エンドポイントは、AMP の CORS リクエストの仕様で指定されている要件を満たしている必要があります。

credentials(オプション) Fetch API で指定されているとおりに credentials オプションを定義します。
  • サポートされている値: 「omit」、「include」
  • デフォルト値: 「omit」
認証情報を送信するには、include を渡します。この値が設定されている場合、レスポンスは AMP CORS セキュリティ ガイドラインに準拠する必要があります。

AMP.setState() によるディープマージ

AMP.setState() が呼び出されると、amp-bind により、指定されたオブジェクト リテラルと現在の状態がディープマージされます。繰り返しマージされるネストされたオブジェクトを除き、オブジェクト リテラルの変数はすべて、状態に直接書き込まれます。状態に含まれているプリミティブと配列は必ず、オブジェクト リテラル内の同じ名前の変数によって上書きされます。

次の例をご覧ください。

{
  <!-- State is empty -->
  }
<button on="tap:AMP.setState({employee: {name: 'John Smith', age: 47, vehicle: 'Car'}})"...></button>
<button on="tap:AMP.setState({employee: {age: 64}})"...></button>

つ目のボタンを押すと、状態が次のように変わります。

{
  employee: {
    name: 'John Smith',
    age: 47,
    vehicle: 'Car',
    }
  }

つ目のボタンを押すと、amp-bind により、オブジェクト リテラルの引数 {employee: {age: 64}} が既存の状態に繰り返しマージされます。

{
  employee: {
    name: 'John Smith',
    age: 64,
    vehicle: 'Car',
    }
  }

employee.age は更新されていますが、employee.name キーと employee.vehicle キーは変更されていません。

循環参照を含むオブジェクト リテラルを指定して AMP.setState() を呼び出すと、amp-bind によってエラーがスローされます。

変数の削除

既存のステータス変数を削除するには、AMP.setState() でその値を null に設定します。前の例の状態から開始して、次のボタンを押します。

<button on="tap:AMP.setState({employee: {vehicle: null}})"...></button>

状態が次のように変わります。

{
  employee: {
    name: 'John Smith',
    age: 48,
    }
  }

同様に、次のボタンを押します。

<button on="tap:AMP.setState({employee: null})"...></button>

状態が次のように変わります。

{
  <!-- State is empty -->
  }

式の文法

amp-bind の式の文法は BNF に似ています。

expr:
  operation
  | invocation
  | member_access
  | '(' expr ')'
  | variable
  | literal

operation:
    !' expr
    | '-' expr
    | '+' expr
    | expr '+' expr
    | expr '-' expr
    | expr '*' expr
    | expr '/' expr
    | expr '%' expr
    | expr '&&' expr
    | expr '||' expr
    | expr '<=' expr
    | expr '<' expr
    | expr '>=' expr
    | expr '>' expr
    | expr '!=' expr
    | expr '==' expr
    | expr '?' expr ':' expr

invocation:
      expr '.' NAME args

args:
  (' ')'
  | '(' array ')'
    ;

member_access:
  expr member
    ;

member:
  .' NAME
  | '[' expr ']'

variable:
   NAME
  ;

literal:
    STRING
  | NUMBER
  | TRUE
  | FALSE
  | NULL
  | object_literal
  | array_literal

array_literal:
    ' ']'
    | '[' array ']'

array:
  expr
  | array ',' expr

object_literal:
  {' '}'
  '{' object '}'

object:
  key_value
  | object ',' key_value

key_value:
  expr ':' expr
ご不明な点がある場合

You've read this document a dozen times but it doesn't really cover all of your questions? Maybe other people felt the same: reach out to them on Stack Overflow.

Go to Stack Overflow
Found a bug or missing a feature?

The AMP project strongly encourages your participation and contributions! We hope you'll become an ongoing participant in our open source community but we also welcome one-off contributions for the issues you're particularly passionate about.

Go to GitHub