AMP環境でハンバーガーメニューを実装する

2 min読了の目安(約2100字TECH技術記事

はじめに

Next.js を使って fullAMP で構築しているブログにハンバーガーメニューを設置しようとしたところ、JavaScript が実行出来ないため AMP コンポーネントを使って実装する必要がありました。

AMP コンポーネントで開閉出来るメニューを探して最初にみつけたのが amp-sidebar でした。
簡単に導入できそうだったのですがサイドバーが左右どちらから出るかしか制御できず、望んでいた動作ではなかったので見送り、もっと自由度の高い実装を見つけたので紹介します。

今回は amp-bind で状態を持ってクラス名の変更が出来たので CSS で開閉するメニューを実装しました。

開閉するメニューの実装

amp-bind の読み込み

head から amp-bind を読み込みます。

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

開閉ボタンの設置

クリックするとメニューを開閉させる要素を作ります。
on 属性で setState することで isOpen という変数に状態を更新することが出来ます。

<a on="tap:AMP.setState({ isOpen: !isOpen })">
  開閉
</a>

TSX で記述している場合は a 要素に on 属性が定義されていないため、下記のような型エラーが発生します。

(JSX attribute) on: string
Type '{ on: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
  Property 'on' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.ts(2322)

そのため、JSX の a 要素の型定義を変更する必要があります。
次のように React.AnchorHTMLAttributes に on 属性を追加しました。

interface AmpAnchorHTMLAttributes<T> extends React.AnchorHTMLAttributes<T> {
  on?: string
}

declare namespace JSX {
  interface IntrinsicElements {
    a: React.DetailedHTMLProps<
      AmpAnchorHTMLAttributes<HTMLAnchorElement>,
      HTMLAnchorElement
    >
  }
}

メニューの設置

メニュー要素では、開閉ボタンで設定した isOpen に合わせてクラス名を変更します。
amp-bind のドキュメントでは [class] 属性に動的にクラス名を指定すればいいとあるのですが、これも JSX では記述できません。

<div [class]="isOpen ? 'open' : 'close'">

かわりに data-amp-bind-class という属性で定義します。
このとき className を完全に上書きするのでこの要素のすべてのクラス名を記述する必要があります。

<div
  data-amp-bind-class="isOpen ? 'menu open' : 'menu close'"
  className="menu close"
>
  <ul>
    <li>メニュ-1</li>
    <li>メニュ-2</li>
    <li>メニュ-3</li>
  </ul>
</div>

open, close それぞれにスタイルを当ててメニューの開閉を実現します。

.menu {
  transition-duration: 500ms;
}
.open {
  height: 400px;
}
.close {
  height: 0;
}

まとめ

これで開閉ボタンをクリックするたびに isOpen が反転し、メニューのクラス名が置き換わるようになりました。

今回使用した amp-bind のドキュメント
https://amp.dev/ja/documentation/components/amp-bind/