WordPress に amp-list の無限スクロール

amp-listの無限スクロールをWordPressに実装する方法

Akira

福岡在住ウェブデザイナー。Web サイト制作に役立つ情報を「見やすさ」と「使いやすさ」にこだわり紹介しています。

2019 年 4 月に、amp-list のアップデートがありました。

アップデートにより、AMP で動的に表示するリストの無限スクロールが可能になりました。

この amp-list の無限スクロールを WordPress に実装する方法の紹介です。

動作

amp-list の無限スクロールの動作は、このような感じです。最初は 3 件を表示し、ボタンのクリックで 3 件ずつデータを追加しています。

amp-list の無限スクロールには 2 つのタイプがあり、ボタンをクリックせずに自動的にデータを追加するタイプもあります。

特徴と用途

amp-list の無限スクロールは、以下の特徴があります。

  • 現在のページに「データ」を追加する
  • 「異なるページ」を追加するわけではないため、ページアドレスは変わらない
  • データ追加後の状態は、ブラウザの履歴に残らない

この特徴は、異なるページを追加する一般的な無限スクロールとは違います。ちなみに、一般的な無限スクロールは、このようなものです。

  • 現在のページに「異なるページ」を追加する
  • 「異なるページ」を追加後、ページアドレスが変わる
  • 追加したページのアドレスが、ブラウザの履歴に残る

一般的な無限スクロールはブラウザの履歴に残るため、ブラウザの「戻る」が使えます。一方、amp-list の無限スクロールは、ブラウザの「戻る」が使えません(「戻る」でデータ追加前の状態に戻らない)。

amp-list の無限スクロールの特徴を考慮すると、このようなリストに適しています。

  • 「関連記事」や「関連商品」のリスト
  • ブログやニュースサイトのコンテンツページで見かける「人気記事」や「最新記事」のリスト
  • 検索結果ページなど「絞り込み」や「並び替え」機能が必要なリスト(これらの機能は amp-bind の併用で実装可能)

一方、不適切だと思うのは、ページネーションのある複数のページに分割した記事一覧リスト(記事一覧ページ)。このようなリストに実装すると、ユーザビリティが悪化する(ブラウザの戻るが使えないため)、SEO に悪影響が出るなどが考えられます。

尚、AMP の記事一覧ページで無限スクロールを実装するには、amp-next-page を使います。ただし、執筆時点では試験運用版のため、まだ本番環境で実装できません。

実装

WordPress での amp-list の無限スクロールの実装は、大まかに 3 ステップで終わります。

今回は、投稿ページに「新着記事」を表示する例として説明します。

この「新着記事」は、最初は 5 件を表示し、無限スクロールで「現在の投稿ページ」を除く全件を作成日順で表示するものです。

WP REST API の独自エンドポイントを作成

最初にすることは、WP REST API の独自エンドポイントの作成です。

amp-list は、JSON から動的にデータを取得します。WordPress の JSON と言えば WP REST API 。

しかし、WP REST API には、amp-list の無限スクロールに必要な「JSON の次のページの URL」が JSON 内に含まれていません。デフォルトでは、HTTP ヘッダに含まれています。

JSON 内に次のページの URL を含めるために、下記コードを functions.php に追加し独自のエンドポイントを作成します。

add_action( 'rest_api_init', function() {
  register_rest_route( 'new', '/id(?P<id>\d+)/page(?P<page>\d+)', [
    'callback' => 'get_rest_api_new',
    'methods'  => WP_REST_Server::READABLE
  ]);
});

function get_rest_api_new( $request ) {
  $request_id   = $request['id'];
  $request_page = $request['page'];
  
  $the_query = new WP_Query([
    'paged'          => $request_page,
    'post__not_in'   => [$request_id],
    'posts_per_page' => 5
  ]);
  
  /**
   * 次ページの URL
   */
  $max_num_pages = $the_query->max_num_pages;
  
  if ( $request_page < $max_num_pages ) {
    $next_page = esc_url( home_url() ) . '/wp-json/new/id' . $request_id . '/page' . ( $request_page + 1 );
  }
  else {
    $next_page = null;
  }
  
  /**
   * 記事データ
   */
  $posts = null;
  
  if ( $the_query->have_posts() ) {
    while ( $the_query->have_posts() ) {
      $the_query->the_post();
      
      $thumb_id  = get_post_thumbnail_id();
      $thumb_src = wp_get_attachment_image_src( $thumb_id, 'large' );
      
      $data['permalink']  = get_permalink();
      $data['title']      = get_the_title();
      $data['cat_name']   = get_the_category()[0]->cat_name;
      $data['srcset']     = wp_get_attachment_image_srcset( $thumb_id );
      $data['src']        = $thumb_src[0];
      $data['img_width']  = $thumb_src[1];
      $data['img_height'] = $thumb_src[2];
      $data['alt']        = get_post_meta( $thumb_id, '_wp_attachment_image_alt', true );
      
      $posts[] = $data;
    }
    wp_reset_postdata();
  }
  
  /**
   * WP REST API へ出力
   */
  $json = [
    'load-more-src' => $next_page,
    'items'         => $posts
  ];
  
  return rest_ensure_response( $json );
}

記事のデータとして、「記事の URL」「記事タイトル」「カテゴリー名」「画像関係」を取得しています。他に必要なデータがあれば、50 行目以降に $data として追加します。

この独自エンドポイントには、このような URL でリクエストできます。

https://example.com/wp-json/new/id50/page1

id50 は、「新着記事」を表示する「現在の投稿ページ」の id です。この id は、<amp-list> タグに設定する the_ID() で取得します。

末尾の page1 は、JSON のページ数です。page1 であれば 1 ページ目、page2 であれば 2 ページ目をリクエストします。

ブラウザのアドレスバーに打ち込むと、このような JSON が返ってきます。

// https://example.com/wp-json/new/id50/page1

{
  "load-more-src": "https://example.com/wp-json/new/id50/page2",
  "items": [
    {
      "permalink": "https://example.com/amp-list/",
      "title": "amp-listの無限スクロール",
      "cat_name": "AMP",
      "srcset": "https://example.com/wp-content/uploads/2019/05/amp-list.png 1920w, 〜(以下、省略)",
      "src": "https://example.com/wp-content/uploads/2019/05/amp-list-640x360.png",
      "img_width": 640,
      "img_height": 360,
      "alt": "amp-listの無限スクロール"
    },
    以下、posts_per_page で指定した件数分の記事データが続く
  ]
}

JSON の次のページの URL は、load-more-src から取得します。また、記事データは、items に格納しています。

この load-more-srcitems の名前は、amp-list の仕様に合わせています。異なる名前にできますが、その場合には <amp-list> タグを作成時に属性の追加が必要です(後述します)。

スクリプトの読み込み

次は、amp-list と amp-mustache のスクリプトを読み込みます。

amp-mustache は、amp-list のテンプレート作成に必要です。

この 2 つのスクリプトを <head> 内で読み込みます。

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

amp-list タグの作成

最後に、<amp-list> タグを作成します。

作成方法や仕様は、公式ページで詳細に解説されています。

amp-list

amp-list example

基本的な作り方は、<amp-list> タグの src 属性に JSON の URL を指定。そして、<template type="amp-mustache"> 内で、JSON のデータを {{}} で囲みテンプレートを作成します。

single.php の「最新記事」を表示したい場所に、このような <amp-list> タグを設置します。

<amp-list layout="fixed-height"
          height="500"
          src="<?php echo esc_url( home_url() ); ?>/wp-json/new/id<?php the_ID(); ?>/page1"
          binding="no"
          load-more="manual">
  <template type="amp-mustache">
    <div>
      <a href="{{permalink}}">
        <div>
          <amp-img src="{{src}}"
                   srcset="{{srcset}}"
                   alt="{{alt}}"
                   width="{{img_width}}"
                   height="{{img_height}}"
                   sizes="(max-width: {{img_width}}px) 100vw, {{img_width}}px">
          </amp-img>
        </div>
        <div>
          <h2>{{title}}</h2>
          <p>{{cat_name}}</p>
        </div>
      </a>
    </div>
  </template>
</amp-list>

amp-list の無限スクロールに必須の属性は、5 行目の load-more です。値は、automanual のいずれかです。

load-more="auto"
自動的にデータを追加する無限スクロール。
load-more="manual"
ボタンのクリックでデータを追加する無限スクロール。デフォルトでは「SEE MORE」ボタンが表示される。

注意点

今回「JSON の次のページの URL」は、JSON の load-more-src から取得しています。この load-more-src は、amp-list がデフォルトで要求する名前です。

もし、WP REST API の独自エンドポイントの作成時に load-more-src を異なる名前にした場合には、<amp-list> タグに load-more-bookmark 属性が必要です。

例えば、名前を next-page にした場合は、load-more-bookmark="next-page" を指定します。これで「next-page から JSON の次のページの URL を取得しなさい」と指示できます。

<amp-list load-more-bookmark="next-page"
          layout="fixed-height"
          height="1000"
          src="<?php echo esc_url( home_url() ); ?>/wp-json/new/id<?php the_ID(); ?>/page1"
          binding="no"
          load-more="auto">

また、記事データは、JSON の items に格納しました。この items も amp-list がデフォルトで要求する名前です。

もし、items を異なる名前にした場合には、<amp-list> タグに items 属性が必要です。

例えば、名前を post-data にした場合は、items="post-data" を指定します。

<amp-list items="post-data"
          layout="fixed-height"
          height="1000"
          src="<?php echo esc_url( home_url() ); ?>/wp-json/new/id<?php the_ID(); ?>/page1"
          binding="no"
          load-more="auto">

オプション

amp-list の無限スクロールには、<amp-list-load-more> タグを用いるオプションが用意されています。

このタグは <amp-list> タグの子要素として配置し、任意の属性を指定します。

公式サイトの amp-list のドキュメントに詳細な説明があるため、ここでは簡単に解説します。

ボタンを変更

load-more="manual" を指定した場合には、デフォルトで「SEE MORE」ボタンが表示されます。

デフォルトのSEE MOREボタン

このボタンを変更できるのが、load-more-button 属性です。

<amp-list> の子要素として、load-more-button 属性を持つ <amp-list-load-more> タグを設置します。

<amp-list layout="fixed-height"
          height="1000"
          src="<?php echo esc_url( home_url() ); ?>/wp-json/new/id<?php the_ID(); ?>/page1"
          load-more="manual">
  <template type="amp-mustache">
    〜省略〜
  </template>
  <amp-list-load-more load-more-button>
    <button>もっと見る</button>
  </amp-list-load-more>
</amp-list>

load-more-button属性を使ったボタンの変更例

amp-mustache を使い、JSON データの反映も可能です。

尚、ボタンの背景色などを変更する場合は、amp-list-load-more タグに対してではなく、button タグに対して CSS を指定します。でなければ、ローディングアニメーションなどにも CSS が適用されてしまいます。

ローディングアニメーション

デフォルトでは、無限スクロール発動時に一般的なローディングアニメーションが適用されます(一部が欠けた円がクルクルと回る)。

独自のローディングアニメーションにしたい時は、load-more-loading 属性を使います。

<amp-list> の子要素として、load-more-loading 属性を持つ <amp-list-load-more> タグを設置します。

<amp-list 以下、省略>
  ...
  <amp-list-load-more load-more-loading>
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
      <path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
      <path d="M0 0h24v24H0z" fill="none"/>
    </svg>
  </amp-list-load-more>
</amp-list>

Material icons の autorenew の SVG を使っています。

あとは、CSS の @keyframes を使えば、独自のローディングアニメーションの完成です。

尚、「読み込み中」などテキストのみでも構いません。

読み込み失敗時の再読み込み

JSON の次のページの読み込みに失敗した時に備えられるのが、load-more-failed 属性です。

この属性を持つ <amp-list-load-more> タグがあると、JSON の読み込みの失敗時にボタンを表示できます。このボタンのクリックで、JSON の再読み込みが可能です。

ボタンを表示するだけなら、このように指定します。

<amp-list 以下、省略>
  ...
  <amp-list-load-more load-more-failed>
    <button>再読み込み</button>
  </amp-list-load-more>
</amp-list>

また、注意文も表示するのなら、ボタン要素に load-more-clickable 属性を指定します(注意文はクリック要素にはならず、ボタンのみがクリック要素となる)。

<amp-list 以下、省略>
  ...
  <amp-list-load-more load-more-failed>
    <p>読み込みに失敗しました</p>
    <button load-more-clickable>再読み込み</button>
  </amp-list-load-more>
</amp-list>

これ以上データがない時の注意文

これ以上読み込む JSON のデータがない時に注意文を表示できるのが、load-more-end 属性です。

「これ以上は記事がありません」などの注意文を表示できます。

<amp-list 以下、省略>
  ...
  <amp-list-load-more load-more-end>
    <p>これ以上は記事がありません</p>
  </amp-list-load-more>
</amp-list>

load-more-end属性の使用例

複数の指定

異なる属性を持つ複数の <amp-list-load-more> タグを設置できます。

<amp-list 以下、省略>
  ...
  <amp-list-load-more load-more-button>
    <button>もっと見る</button>
  </amp-list-load-more>
  <amp-list-load-more load-more-end>
    <p>これ以上は記事がありません</p>
  </amp-list-load-more>
</amp-list>