この Blog のデザインをリニューアルしたとき (参考エントリー) に、AMP (Accelerated Mobile Pages) のデザインを、通常ページと一致させました。
その際、通常ページのヘッダにあるサイト内検索部分の UI (検索キーワード入力欄を初期状態では非表示、検索のアイコンを押すことで表示される) を、AMP でも同様に再現する必要がありましたが、これに拡張 AMP コンポーネントの amp-bind を使ってみたら超簡単で手軽に実装できたというお話。
当初は amp-script
を使ってみる予定でした
実はこの部分の実装をするって考えたとき、一番最初は、AMP 上で任意の JavaScript を実行可能にする amp-script 拡張コンポーネントを使って実装してみようかなと思っていたんです。
で、いざ仕様を調べようとドキュメントを読んでいるときに amp-bind
の項目をみて、ボタンクリックで特定要素に特定の class 属性値を付けて操作、みたいな簡易な UI であれば、わざわざ amp-script
を使うのも面倒だな、amp-bind
を使う方が簡単じゃん、ってことでこちらを選択して実装してみることに。
ちなみに amp-bind
自体は別に目新しい機能じゃないです。確か数年前から存在してると思いますが、単に私が AMP の実装を最近行っていなかったために使ったことがなかったというだけの話です。
amp-bind
を使ってみる
まずは AMP の head 要素内で amp-bind
用のスクリプトを読み込みます。
AMP の仕様が出始めたころって、確か、拡張 AMP コンポーネントの script
要素は、必須の AMP ライブラリよりも前に記述しろ、みたいな仕様になってたんですけども (いつの話だ......)、今ってこの読み込みの順番、特に指定なくなってるんですね。なので、この Blog では必須ライブラリに続けて記述しています。
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<meta name="description" content="This is the AMP Boilerplate.">
<link rel="preload" as="script" href="https://cdn.ampproject.org/v0.js">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<!-- amp-bind 用のスクリプト -->
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
<style amp-custom>
/* Add your styles here */
</style>
<style amp-boilerplate>...略...</style><noscript><style amp-boilerplate>...略...</style></noscript>
<link rel="canonical" href=".">
<title>My AMP Page</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
ボタンを押したら class が付与されるコードのサンプル
下記がこの Blog のヘッダで使用している実際のコード (わかりやすいように一部、説明に不要な class や属性などは除いていますが) ですが、これだけでもなんとなくわかるんじゃないかってくらい単純なコードですね。
<div class="module-header-search" [class]="state">
<div class="module-header-search-ui">
<button on="tap:AMP.setState({state:'module-header-search state-amp-active'})"
class="module-header-search-open-btn"
title="検索フォームを開く">
<span class="material-icons">search</span>
</button>
<button on="tap:AMP.setState({state:'module-header-search'})"
class="module-header-search-close-btn"
title="検索フォームを閉じる">
<span class="material-icons">close</span>
</button>
</div>
<form class="module-search" method="get" target="_top" action="/s/" role="search" aria-label="サイト内検索">
...略...
</form>
</div>
[class]
という、class を操作するためのバインディングがあらかじめ用意されていますので、これを使用。あとは同じく amp-bind
に用意されている、AMP.setState()
を使用して、先ほどの [class]
バインディングに対して状態の変更を渡してあげます。
具体的には、class を変更したい要素に対して [class]="state"
としておいて、button
要素に on="tap:AMP.setState({state:'module-header-search state-amp-active'})"
などとすることで、button
要素をクリックすると、[class]
を指定した要素の class 属性値が、AMP.setState()
で渡したものと差し替わります。
この時の動作は、JavaScript でいう、Element.classList.replace()
と同等になりますので、元々付与されていた class 属性値は削除されます。Element.classList.add()
と同等の動作を期待すると異なりますので注意しましょう。
あとは、
.module-header-search.state-amp-active {
...略...
}
みたいにスタイルを書いてあげればよいと。
実際の動作としては下記のような感じになります。
おまけ
ちなみに、今回はやってないですが、ボタン自体は 1つで、それをクリック (タップ) する度に、ある要素に対して class 属性値を付けたり外したりしたいという場合もあると思います。要するに Element.classList.toggle()
に近いような動作をさせたい場合ですね。そういう時は下記のようにすれば感覚的には近い動作にすることができると思います。
<button on="tap:AMP.setState({state: !state})">
class 属性値 切替ボタン
</button>
<div class="foo" [class]="state ? 'foo is-active' : 'foo'">
...text...
</div>
class 属性値を操作する要素が class 属性を持たない場合は、下記のようにすればよいでしょう。
<button on="tap:AMP.setState({state: !state})">
class 属性値 切替ボタン
</button>
<div [class]="state ? 'is-active' : ''">
...text...
</div>
まとめ
ということで、もっと複雑な DOM の操作などを行いたい場合は amp-script
を選択すればよいと思いますが、単純に class 属性を操作したい、テキストの一部を変更したい程度の話であれば、amp-bind
はとても簡単に使えて便利です。
また、amp-list 拡張コンポーネントと組み合わせて、JSON からデータをバインドするなんて使い方もできるので、例えばよくある、記事リストで初期には 10件だけ表示しておいて、「続きを読み込む」 ボタンなどでさらに 10件追加表示する、みたいな UI や、記事のリストをカテゴリでフィルタリングしたり、みたいな機能も JavaScript をわざわざ書かなくても実装できたりします。
めちゃくちゃ久しぶりに AMP を実装したんですが、いい機会なのでもうちょっと深掘りしてみようかな、なんて思いました。