【WordPress】Ajaxを使った無限スクロールを実現する方法

WordPressのブログやメディアサイトで、ページネーションではなく無限スクロールを導入すると、ユーザーの体験が向上します。この記事では、Ajaxを使って無限スクロールを実現する方法を具体的に解説します。

スポンサーリンク

JavaScriptでAjaxリクエストを送信

無限スクロールを実現するJavaScriptを用意します。jQueryを使用して、スクロール時に次の記事を非同期で取得します。

NG:読み込み中フラグが無く多重リクエストが飛ぶ
jQuery(document).ready(function ($) {
  let currentPage = 1; // 現在のページ
  const maxPage = parseInt(ajax_object.max_page); // 最大ページ数
  const ajaxUrl = ajax_object.ajax_url; // Ajax URL

  // 無限スクロール
  $(window).on('scroll', function () {
    if ($(window).scrollTop() + $(window).height() >= $(document).height() - 300) {
      if (currentPage < maxPage) {
        currentPage++;
        $.ajax({
          url: ajaxUrl,
          method: 'POST',
          data: {
            action: 'load_more_posts',
            page: currentPage,
          },
          beforeSend: function () {
            $('#loading-icon').show(); // ローディングアイコンを表示
          },
          success: function (response) {
            if (response) {
              $('#post-container').append(response); // 記事を追加
              $('#loading-icon').hide(); // ローディングアイコンを非表示
            } else {
              $('#loading-icon').hide();
            }
          },
        });
      }
    }
  });
});
// 読み込み中フラグが無いため、scrollイベントの多重発火で複数ページ分のリクエストが同時に飛ぶ
読み込み中フラグが無いと同時に複数ページ分のリクエストが飛ぶ
scrollイベントは短時間に何度も発火します。前のAjaxリクエストが完了して$(document).height()が伸びる(=トリガーゾーンから外れる)前に、次のscrollイベントが発火するとcurrentPageが連続でインクリメントされ、複数ページ分のリクエストが同時に送信されてしまいます(ページ飛ばし・記事の重複表示・無駄なサーバー負荷の原因)。isLoadingのようなフラグでリクエスト中の再入力をガードする必要があります。
OK:isLoadingフラグでガード
jQuery(document).ready(function ($) {
  let currentPage = 1; // 現在のページ
  let isLoading = false; // 読み込み中フラグ
  const maxPage = parseInt(ajax_object.max_page); // 最大ページ数
  const ajaxUrl = ajax_object.ajax_url; // Ajax URL

  // 無限スクロール
  $(window).on('scroll', function () {
    if (isLoading) return; // 読み込み中は何もしない

    if ($(window).scrollTop() + $(window).height() >= $(document).height() - 300) {
      if (currentPage < maxPage) {
        isLoading = true;
        currentPage++;
        $.ajax({
          url: ajaxUrl,
          method: 'POST',
          data: {
            action: 'load_more_posts',
            page: currentPage,
          },
          beforeSend: function () {
            $('#loading-icon').show(); // ローディングアイコンを表示
          },
          success: function (response) {
            if (response) {
              $('#post-container').append(response); // 記事を追加
            }
          },
          complete: function () {
            // 成功・失敗にかかわらず必ず後始末する
            $('#loading-icon').hide();
            isLoading = false;
          },
        });
      }
    }
  });
});

上記コードでは、スクロール位置がページ下部に近づいたとき、読み込み中でなければ次の記事を取得して表示します。

PHPで記事データを返す

functions.php に以下のコードを追加して、Ajaxリクエストを受け取り、記事データを返す処理を作成します。

functions.php:Ajaxハンドラーで記事データを返す
function load_more_posts() {
  $paged = isset($_POST['page']) ? intval($_POST['page']) : 1;

  $query_args = array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'paged'          => $paged,
  );
  $query = new WP_Query($query_args);

  if ($query->have_posts()) {
    while ($query->have_posts()) {
      $query->the_post();
      echo '<article class="post">';
      echo '<h2><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></h2>';
      echo '<p>' . esc_html(get_the_excerpt()) . '</p>';
      echo '</article>';
    }
  }
  wp_reset_postdata();
  wp_die();
}
add_action('wp_ajax_load_more_posts', 'load_more_posts');
add_action('wp_ajax_nopriv_load_more_posts', 'load_more_posts');

このコードは、指定されたページ番号に基づいて記事データを取得し、HTML形式で返します。

Ajax用のデータを渡す

functions.php に以下のスクリプトを追加して、フロントエンドに必要なデータを渡します。

NG:子テーマ環境ではJSファイルが404になる
function enqueue_infinite_scroll_scripts() {
  wp_enqueue_script('infinite-scroll', get_template_directory_uri() . '/js/infinite-scroll.js', array('jquery'), null, true);

  wp_localize_script('infinite-scroll', 'ajax_object', array(
    'ajax_url' => admin_url('admin-ajax.php'),
    'max_page' => ceil(wp_count_posts()->publish / 5), // 最大ページ数
  ));
}
add_action('wp_enqueue_scripts', 'enqueue_infinite_scroll_scripts');
// 子テーマ環境ではURLが親テーマを指してしまい infinite-scroll.js が404になる
get_template_directory_uri() は親テーマのURLを返す
get_template_directory_uri()親テーマのディレクトリURLを返す関数です。子テーマを使っている環境でinfinite-scroll.jsを子テーマ側に置くと、URLが親テーマを指してしまいスクリプトが404になり、無限スクロールが一切動作しません。子テーマのURLを正しく取得するにはget_stylesheet_directory_uri()を使います。
OK:get_stylesheet_directory_uriで子テーマでも正しく読み込む
function enqueue_infinite_scroll_scripts() {
  wp_enqueue_script('infinite-scroll', get_stylesheet_directory_uri() . '/js/infinite-scroll.js', array('jquery'), null, true);

  wp_localize_script('infinite-scroll', 'ajax_object', array(
    'ajax_url' => admin_url('admin-ajax.php'),
    'max_page' => ceil(wp_count_posts()->publish / 5), // 最大ページ数
  ));
}
add_action('wp_enqueue_scripts', 'enqueue_infinite_scroll_scripts');

これにより、ajax_object 変数を通じて ajax_url と max_page をJavaScriptに渡します。

HTMLで投稿リストを表示

記事リストを表示するHTMLを用意します。

page.php:初期表示の投稿リスト
<div id="post-container">
  <?php
  $query = new WP_Query(array(
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'paged'          => 1,
  ));
  if ($query->have_posts()) {
    while ($query->have_posts()) {
      $query->the_post();
      ?>
      <article class="post">
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
        <p><?php the_excerpt(); ?></p>
      </article>
      <?php
    }
    wp_reset_postdata();
  }
  ?>
</div>
<div id="loading-icon" style="display: none;">
  <p>読み込み中...</p>
</div>

このコードでは、最初の5件の記事を表示し、Ajaxで読み込んだ記事を #post-container に追加します。

スタイルの調整

ローディングアイコンのデザインを追加します。

style.css:ローディングアイコンのスタイル
#loading-icon {
  text-align: center;
  margin: 20px 0;
  font-size: 14px;
  color: #555;
}

関連記事

よくある質問(FAQ)

QWordPressでAjaxを使った無限スクロールの基本的な仕組みは?
Aスクロールイベントでページ末尾を検知し、jQuery.ajax()wp-admin/admin-ajax.phpにリクエストを送ります。PHPのAjaxハンドラーでWP_Queryを実行して記事HTMLを返し、JavaScriptがDOMに追加します。リクエスト中に再度リクエストが飛ばないよう、読み込み中フラグでガードするのがポイントです。
Qwp_localize_script()でAjaxのURLをJavaScriptに渡すには?
Awp_localize_script('my-script', 'myVars', array('ajaxUrl' => admin_url('admin-ajax.php')))でJavaScript内からmyVars.ajaxUrlとしてAjaxエンドポイントURLにアクセスできます。子テーマを使っている場合はスクリプトのURL取得にget_stylesheet_directory_uri()を使ってください。
QAjaxの無限スクロールで最後のページ以降のリクエストを止めるには?
APHPのAjaxハンドラーでWP_Querymax_num_pagesと現在のページ数を比較し、最後のページの場合はhas_more: falseのようなフラグをJSONで返します。JavaScriptでこのフラグを確認してイベントリスナーを解除します。

まとめ

WordPressでAjaxを使った無限スクロールを実装することで、ユーザー体験を向上させることができます。子テーマ環境ではget_stylesheet_directory_uri()を使うこと、スクロールイベントには読み込み中フラグでガードを入れることの2点に注意すれば、この方法はシンプルながら柔軟性が高く、カスタマイズも容易です。