next.jsで作ってみたブログにamp-sidebarを導入する

published: 2020/9/29 update: 2020/9/29

Table of Contents

TL;DR

モバイルページでもサイドバーはやはりほしい。そして最近のはやりはfloating buttonみたいなやつを押すとサイドバーが開く、というものである...気がする。もちろん、onClickやらを使えばかんたんに実装できるのだが、ampに対応しているとonClickは許されていない。

そういうときに使えるのがamp-sidebarである。しかし、Reactやnext.jsでamp-sidebarを導入している事例は少なく、material-uiやtypescriptと一緒にやっている例は見つからなかった。一応実装できたので、参考になる人がいることを祈って記事に残しておく。

amp-sidebar

普段は隠れていて、ボタンを押すと表示され、サイドバー以外の部分を押すと閉じる、という機能がデフォルトで実装されている。 とりあえず公式の例を見てみる。

<amp-sidebar id="sidebar1" layout="nodisplay" side="right">
  <ul>
    <li>Nav item 1</li>
    <li><a href="#idTwo" on="tap:idTwo.scrollTo">Nav item 2</a></li>
    <li>Nav item 3</li>
    <li><a href="#idFour" on="tap:idFour.scrollTo">Nav item 4</a></li>
    <li>Nav item 5</li>
    <li>Nav item 6</li>
  </ul>
</amp-sidebar>

<button class="hamburger" on='tap:sidebar1.toggle'></button>

基本的には、amp-sidebaridを指定し、buttonのontap:{id}.toggleをつければ、そのボタンで開閉ができるようになる。このtoggleの部分は他にも可能で

actiondesc
open (default)サイドバーを開く
closeサイドバーを閉じる
toggleサイドバーを開閉する

の3つが使える。基本的にtoggleでいい気がする。

なので、

<amp-sidebar id="sidebar1">{children}</amp-sidebar>
<button on="tap:sidebar1.toggle">toggle</button>

のようなjsxを書けばいいことがわかる。

しかし、buttonにon属性はないので、Typescriptを使う場合はonを型定義する必要があることに注意が必要(ts-ignoreでもいいかもしれないが...)。

Float Button

こちらは簡単で@material-ui/core/Fabを使えばOK。ただ、このままだと場所が固定されておらず、onが定義されていないのでそのへんを定義する必要がある。

まず型定義は基本的に同じところからexportされているxxxPropsというものを使う。今回の場合はFabPropsFabと一緒にimportする。このbuttonはonを必ず使う用途だと考えているのでdefaultpropsの拡張は行っていない。

AmpFab.tsx
import React from "react";

import Fab, { FabProps } from "@material-ui/core/Fab";

interface AmpOnProps {
  on: string;
}

type Props = FabProps & AmpOnProps;

const AmpFab: React.FC<Props> = (props) => {
  return <Fab {...props} />;
};

export default AmpFab;

場所の定義はここでやってしまってもいいが、amp-sidebarAmpFabをあわせてAmpSidebarコンポーネントを作成したかったので、そこで定義することにした。

AmpSidebar(amp-sidebar+float button)

画面が大きいときは固定したサイドバーを表示するので、固定したサイドバーが表示されなくなったときにFabが表示されるように設定してある。右下に固定するのに必要な部分は以下のcss部分。

margin: 0;
top: "auto";
right: 20;
bottom: 20;
left: "auto";
position: "fixed";

注意が必要なのは、amp-sidebar<body>の直下にないとだめなので、<div>などで囲ってしまうと、Warningが表示される。なので、Fragmentで囲う必要がある。

AmpSidebar.tsx
import React from "react";

import AmpFab from "./AmpFab";

import { createStyles, Theme, makeStyles } from "@material-ui/core/styles";
import NavigationIcon from "@material-ui/icons/Navigation";

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    fab: {
      display: "block",
      [theme.breakpoints.up("sm")]: {
        display: "none",
      },
      margin: 0,
      top: "auto",
      right: 20,
      bottom: 20,
      left: "auto",
      position: "fixed",
    },
  })
);

const AmpSidebar = ({ children }) => {
  const classes = useStyles();
  return (
    <>
      <AmpFab
        on="tap:ampsidebar.toggle"
        variant="extended"
        aria-label="amp-fab"
        className={classes.fab}
      >
        <NavigationIcon>Navigation</NavigationIcon>
      </AmpFab>
      <amp-sidebar id="ampsidebar" className="ampsidebar" layout="nodisplay">
        {children}
      </amp-sidebar>
    </>
  );
};

AmpSidebar.defaultProps = {
  children: <></>,
};

export default AmpSidebar;

Layoutにimportする

デフォルトの_document.jsは以下である。このサイトでは、_document.jsampsidebarを直接入れる必要がある、とされている。しかし、このpagesの中身が入る部分である<Main>はfragmentで囲われたものなので、この中にamp-sidebarを入れてもWarningは表示されない。ただし、material-uiのContainerやもっと単純にdivなどで囲ってしまうとWarningが表示されるので、できるだけ上の方のコンポーネントにamp-sidebarを入れる必要がある。

_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

例えば、以下のようにする。これを標準レイアウトにすればWarningは表示されない。

Layout.tsx
import Header from "./Header";
import Footer from "./Footer";
import AmpSidebar from "./AmpSidebar"

const Layout = ({ children }) => {
  return (
    <>
      <Header />
      {children}
      <AmpSidebar />
      <Footer />
    </>
  );
};

export default Layout;

参考

記事に間違い等ありましたら、お気軽に以下までご連絡ください

E-mail: illumination.k.27|gmail.com ("|" replaced to "@")

Twitter: @illuminationK

当HPを応援してくれる方は下のリンクからお布施をいただけると非常に励みになります。

Ofuse

Other Articles

Site Map

Table of Contents

    TL;DR

    amp-sidebar

    Float Button

    AmpSidebar(amp-sidebar+float button)

    Layoutにimportする

    参考


当HPを応援してくれる方は下のリンクからお布施をいただけると非常に励みになります。

Ofuse
Privacy Policy

Copyright © illumination-k 2020-2021.