Ownedmedia ウェブ制作に役立つコンテンツを発信中!

WordPress 2020.08.24

WordPressでサブループを使った非同期での無限ローディング機能を作成してみる

Tags: ,,,,,

先日、WordPress案件で記事一覧ページで無限ローディングの要件があり、いろんな情報記事を参考に作成してみるも上手くいかず、色々と試行錯誤を重ねたものを備忘録としてまとめておきたいと思います。WordPressで無限ローディングを実装するには「Infinite-Scroll」などのプラグインが有名ですが、投稿記事取得の条件が特殊だったり、カスタマイズ性が求められるということでとりあえず自前で実装する方向で進めてみました。

 

無限ローディングの方法にはスクロールを基準に非同期通信が走るものもありますが、今回はボタンをクリックして記事を取得する方法で作成していきました。投稿記事が無くなった場合にはボタンも非表示になるようにしています。また記事取得の条件はサブループを使用して、いろんなケースに対応できるようになっています。

 

非同期で投稿記事を読み込む

通常、WordPressではURLにアクセスすることで、そのURLのルーティングに合わせた情報をデータベースから取得する形になります。もしくはサブクエリという形で、ページ上に書かれた処理を読み込むことで同じくデータベースから取得されます。それに対して非同期通信では、スクロールやボタンクリックなどのイベントで投稿記事をデータベースから取得させることができます。その際にページが読み込まれるといったことはありませんので、より動的な印象で記事を読み込むことができます。

 

そのため、記事を取得するPHPファイルだけではなく、イベントで非同期通信を実行させるためのJavaScriptファイルも必要になってきます。具体的には下記のような流れで記事が取得されます。

1. JavaScriptでイベントに合わせて取得件数などの情報がPHPファイルへPOSTされる
2. PHP側で取得件数などの情報をもとにサブクエリが実行されて記事情報が取得される
3. 取得した記事情報をPHP側でJSON形式で出力させることでJavaScript側にレスポンスとして渡される
4. JavaScriptでレスポンスで受け取った記事情報をHTML上へ動的に表示させる

 

上記の流れでどんどん繰り返して記事が存在する限り無限にローディングを行うことができます。それでは次に実際のコードを見ていきます。

 

無限ローディング機能の実装

今回の実装には下記の通り最低3つのファイルが必要となります。それぞれのファイル内にあるソースコードと役割を見てきます。

・ index.php(ページテンプレート)
・ loading.php(ローディング用のループ出力)
・ post.js(非同期でローディング実行)

 

まずは無限ローディングで記事一覧を表示させるためのページテンプレートです。今回は「index.php」を使っていますが、固定ページなどのページテンプレートでも可能です。ただ、実質は記事一覧ページとなりますのでアーカイブページが好ましいかもしれませんね。ページテンプレートに記述するのはとてもシンプルなHTMLだけになります。記事のアーカイブを表示させるコンテナ要素と、ローディングを実行するためのボタン要素を設置します。JavaScriptでDOM操作をするため適宜Id属性などを設定しておきます。

【index.php】※一部抜粋

..............
<div id="infinite_loading_container">
  <!-- ここに記事のアーカイブが表示されます -->
</div>
<button id="infinite_loading_button">もっと読み込む</button>
..............

 

続いて、記事情報を取得するためのサブループが書かれたPHPファイルです。このファイルは基本的にページテンプレートなどでインクルードさせず、JavaScriptからのPOST通信を受けるためのファイルになります。まずはデフォルトであらかじめ表示されている記事件数と、追加で読み込む記事数を変数に入れておきます。カスタム投稿などの投稿タイプといったクエリ条件が通常と異なる場合には変数に入れておくと便利です。そして「wp-load.php」ファイルをインクルードしておきます。こうすることでこのファイル内でWordPressで扱う関数やグローバル変数が使えるようになります。以降、ローディングのための処理が続いていきます。

【loading.php】

<?php
  $default_show_posts = 3;
  $default_loading_posts = 3;
  $target_post_type = 'post';
  require_once(dirname(__FILE__).'/../../../../../wp-load.php');
  $offset_value = isset($_POST['currently_loaded_count']) ? $_POST['currently_loaded_count'] : $default_show_posts;
  $loading_count = isset($_POST['additional_loading_count']) ? $_POST['additional_loading_count'] : $default_loading_posts;
  $all_posts_query = new WP_Query(
    array(
      'post_type' => $target_post_type,
      'posts_per_page' => -1
    )
  );
  $infinite_loading_query = new WP_Query(
    array(
      'post_type' => $target_post_type,
      'posts_per_page' => (int)$loading_count,
      'offset' => (int)$offset_value
    )
  );
  $posts_count = $all_posts_query->found_posts;
  if($infinite_loading_query->have_posts()):
?>
<?php 
  while($infinite_loading_query->have_posts()): 
  $infinite_loading_query->the_post(); 
?>
<?php 
  $posts = $infinite_loading_query->posts;
  $remaining_count = $posts_count - $offset_value - 1;
  $contents = array();
  foreach ($posts as $post) {
    $html = '<div>'.$post->post_title.'</div>';
    $html .= '<a href="'.get_permalink($post->ID).'">'.get_permalink($post->ID).'</a>';
    if(has_post_thumbnail($post->ID)) {
      $html .= '<img src="'.get_the_post_thumbnail_url($post->ID, 'full').'" alt="'.$post->post_title.'">';
    } else {
      $html .= '<img src="********.jpg" alt="'.$post->post_title.'">';
    }
    $html .= '<div>'.get_the_excerpt($post->ID).'</div>';;
    array_push($contents, $html);
  }
?>
<?php endwhile; ?>
<?php 
  $loading_complete = false;
  if($remaining_count < $loading_count) {
    $loading_complete = true;
  }
  echo json_encode(
    array(
      'complete'=>$loading_complete,
      'content'=>$contents
    )
  );
  endif; 
?>
<?php wp_reset_postdata(); ?>

 

ローディングのための処理ですが、初めにJavaScriptからPOSTされた値を変数に入れます。$_POST変数に値が入るので、入っている場合には送られてきた値を代入し、POSTされていない場合には先ほど設定した初期値が入るようにしておきます。

$offset_value = isset($_POST['currently_loaded_count']) ? $_POST['currently_loaded_count'] : $default_show_posts;
$loading_count = isset($_POST['additional_loading_count']) ? $_POST['additional_loading_count'] : $default_loading_posts;

 

そして、これらの値を使ってサブクエリを作成していきます。その前にまずは対象となる記事の全件数を取得する必要があるので、全件数取得用のサブクエリを作成します。合わせて、サブクエリで取得した記事情報からトータルの記事数を変数に入れておきます。そのあとに、JavaScriptで取得した値を使ったローディング用のサブクエリを作成します。こうすることで、JavaScriptで登録したイベントに合わせてサブクエリが走るという形になります。

$all_posts_query = new WP_Query(
  array(
    'post_type' => $target_post_type,
    'posts_per_page' => -1
  )
);
$infinite_loading_query = new WP_Query(
  array(
    'post_type' => $target_post_type,
    'posts_per_page' => (int)$loading_count,
    'offset' => (int)$offset_value
  )
);
$posts_count = $all_posts_query->found_posts;
if($infinite_loading_query->have_posts()):

 

そのあとは先ほどのクエリでサブループを展開してきます。この時にテンプレート上に出力するのではなく、取得した記事情報を変数に入れるようにしておきます。投稿は複数取得する場合もあるので配列の形にしておくことが重要です。記事情報も複数の場合を想定してforeachで取得し、最後に記事情報を格納する配列型の変数に代入していきます。記事情報は必要に応じてHTMLで構成されるように文字列を組んでいきましょう。

<?php 
  while($infinite_loading_query->have_posts()): 
  $infinite_loading_query->the_post(); 
?>
<?php 
  $posts = $infinite_loading_query->posts;
  $remaining_count = $posts_count - $offset_value - 1;
  $contents = array();
  foreach ($posts as $post) {
    $html = '<div>'.$post->post_title.'</div>';
    $html .= '<a href="'.get_permalink($post->ID).'">'.get_permalink($post->ID).'</a>';
    if(has_post_thumbnail($post->ID)) {
      $html .= '<img src="'.get_the_post_thumbnail_url($post->ID, 'full').'" alt="'.$post->post_title.'">';
    } else {
      $html .= '<img src="********.jpg" alt="'.$post->post_title.'">';
    }
    $html .= '<div>'.get_the_excerpt($post->ID).'</div>';;
    array_push($contents, $html);
  }
?>
<?php endwhile; ?>

 

続けて読み込みが最終になるかどうかのフラグを用意し、取得した記事情報と合わせてJSON形式で出力するようにします。こうすることでJavaScript側でレスポンスとして受け取ることができます。サブループなので最後に「wp_reset_postdata()」をすることを忘れないようにしましょう。

<?php 
  $loading_complete = false;
  if($remaining_count < $loading_count) {
    $loading_complete = true;
  }
  echo json_encode(
    array(
      'complete'=>$loading_complete,
      'content'=>$contents
    )
  );
  endif; 
?>
<?php wp_reset_postdata(); ?>

 

以上でPHPファイルが作成できました。続けてJavaScriptファイルも作成してきます。まずはPOSTする対象となるPHPファイルのパスを絶対パスで変数に入れておきます。そして、初期表示されている数(つまり投稿取得を開始する位置)と、追加読み込みで取得する投稿記事数を変数に入れておきます。あとはトリガーとなるボタン要素と、表示させる先のコンテナ要素もDOMで取得しておきます。

【post.js】

const api_url = 'https://example.com/*******/wp-content/themes/*******/loading.php';   // POST先のPHPファイルパス
let current = 3;   // 初期表示投稿数(投稿取得開始位置)
const add = 3;   // 投稿取得数
const trigger = document.getElementById('infinite_loading_button');   // トリガー要素指定
const container = document.getElementById('infinite_loading_container');   // 表示コンテナ要素指定
trigger.addEventListener('click', () => {
  fetch(api_url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    body: `currently_loaded_count=${current}&additional_loading_count=${add}`
  })
  .then((response) => {
    return response.json();
  })
  .then((json) => {
    json.content.forEach((item) => {
      container.insertAdjacentHTML('beforeEnd', item);
    })
    current = current + add;
    if(json.complete) {
      trigger.remove();
    }
  })
  .catch((error) => {
    return error.message;
  });
});

 

そしてイベントでローディングが実行されるように関数を作成していきます。まずはトリガー要素をクリックすると投稿開始位置と追加で取得する投稿数の情報が、対象のPHPファイルにPOST通信で送られるようにします。ここでは「fetch API」のメソッドを使っていますがjQueryのajaxメソッドを使う方法もあるようですね。(fetchを使う場合にはIE対応が別途必要となるので注意)PHP側で$_POST変数として受け取るためには、リクエストヘッダーに「application/x-www-form-urlencoded」を追加するのと、リクエストボディでは「key=value&key=value」の形にしておくことが重要です。

trigger.addEventListener('click', () => {
  fetch(api_url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    body: `currently_loaded_count=${current}&additional_loading_count=${add}`
  })
  .then((response) => {
    return response.json();
  })
  .then((json) => {
    json.content.forEach((item) => {
      container.insertAdjacentHTML('beforeEnd', item);
    })
    current = current + add;
    if(json.complete) {
      trigger.remove();
    }
  })
  .catch((error) => {
    return error.message;
  });
});

 

fetchメソッドでは返り値がPromiseオブジェクトで返ってくるので、then()で続く処理を書いていきます。レスポンスとして投稿記事の情報を受け取ることができるので、それを展開し、HTML上に出力していくという処理を続けていきます。投稿記事データは配列型になっているので、forEach()などで対応しないとうまく記事が表示できないので注意します。そのあとに投稿開始位置の値を更新し、最終のローディングのフラグで条件分岐させてトリガー要素を削除するという処理を追加しておきます。こうすることで、最終の読み込みが終わるとボタンが非表示となります。

 


 

少し長くなりましたが、今回はWordPressにおいて非同期で投稿記事を読み込む無限ローディング機能の実装についてまとめてみました。色々と方法はあるようなのですが、目的とする要件に合わせて実装するのがいいのではないでしょうか。今回のものはシンプルな形ですのでいろんなカスタマイズにも対応しやすいかと思います。通常のページネーションとは異なるユーザー体験を与えることができますので、ぜひ一度試してみてはいかがでしょうか。

 

(参考にさせていただいたサイト)
WordPressで無限スクロールで記事を読み込む方法!【プラグインなし編】
WordPressの記事をAjaxで追加読み込みしたときの話

 

この記事を書いた人

オガワ シンヤ

合同会社デザインサプライ代表兼CEO / ディレクター・ウェブデザイナー・フロントエンドエンジニアをやっています。「ウェブとデザインでヒト・モノ・サービスを繋げ新しい価値を生み出す」をコンセプトに日々奮闘中!制作中はチョコレートが欠かせない三十路Webクリエイター。

  • Twitter

コメントフォーム

記事に関するご質問やご意見などありましたら下記のコメントフォームよりお気軽に投稿ください。なおメールアドレスは公開されませんのでご安心ください。

内容に問題なければ、お名前・ハンドルネームとメールアドレスを入力いただき、下記の「コメントを送信」ボタンを押してください。

CAPTCHA


この記事もよく読まれています

Scroll to Top
ご質問・ご相談はありませんか ?