はじめに (対象読者・この記事でわかること)

この記事は、PHPでWebアプリケーション開発を行っており、ページネーションの実装に課題を感じている方、またはユーザーインターフェース(UI)やユーザーエクスペリエンス(UX)の改善を目指したい方を対象としています。

この記事を読むことで、PHPを使って多数のページがある場合に、全てのページ番号を表示するのではなく、「...」のような省略記号を用いてページ番号を適切に表示・省略する方法を理解し、実際に実装できるようになります。これにより、ユーザーにとって見やすく、操作しやすい洗練されたページネーションを実現し、Webアプリケーションのユーザビリティを大きく向上させることができます。

Webサイトやアプリケーションで表示するデータ量が増えるにつれて、ページネーションは不可欠な機能となります。しかし、ただページ番号を並べるだけでは、ページ数が膨大になった際にUIが煩雑になり、ユーザーの使い勝手が悪くなってしまいます。この記事では、この課題を解決するための具体的な手法をご紹介します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - PHPの基本的な構文とWebアプリケーション開発の基礎 - HTML/CSSの基本的な知識 - データベースからデータを取得する(SQLのLIMITOFFSET句の概念) - 配列操作や条件分岐(if/else)、ループ(for)といった基本的なプログラミング概念

ページネーションにおける「...」の重要性とデザイン原則

ページネーションは、大量のデータをユーザーが効率的に閲覧できるようにするために不可欠なUI要素です。しかし、総ページ数が数十、数百、あるいは数千にも及ぶ場合、全てのページ番号を並べて表示することは現実的ではありません。UIが複雑になり、ユーザーは目的のページを見つけにくく、操作性が著しく低下してしまいます。

ここで「...」という省略記号の導入が非常に重要になります。省略記号は、「ここに他にもページがあるが、表示を省略している」ということをユーザーに視覚的に伝える役割を果たします。これにより、以下のようなメリットが生まれます。

  1. UIのシンプル化: ページ番号の羅列が減り、全体的にすっきりとしたデザインになります。
  2. 可読性の向上: 必要なページ番号(現在地、その周辺、最初と最後)が明確になり、ユーザーは素早く情報を把握できます。
  3. ユーザビリティの向上: ユーザーは目的のページへ直感的にアクセスできるため、ストレスなくコンテンツを閲覧できます。

一般的に、省略記号は以下のデザイン原則に基づいて使用されます。 - 現在のページを中心に表示: 現在のページ番号の前後数ページは常に表示します。 - 最初と最後のページは常に表示: ユーザーが全体の範囲を把握できるよう、最初(1ページ目)と最後(総ページ数)は省略せず表示します。 - 必要に応じて「...」を挿入: 現在のページ周辺の表示範囲と、最初・最後のページとの間にギャップがある場合にのみ「...」を挿入します。

このデザイン原則をPHPのロジックで実現することで、ユーザーフレンドリーなページネーションを構築できます。

PHPで「...」を含むページネーションを実装する

ここからは、PHPを使って「...」の省略記号を含むページネーションを実装する具体的な手順とコードを解説します。

ステップ1: ページネーションの基本情報と変数設定

まず、ページネーションの計算に必要な基本情報を設定します。これらの値は、データベースからの取得結果やURLのGETパラメータから動的に決定されることが一般的です。

Php
<?php // --- 基本情報の設定 --- $totalItems = 1000; // 総アイテム数 (例: データベースの総レコード数) $itemsPerPage = 10; // 1ページあたりの表示アイテム数 $currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1; // 現在のページ番号 (URLから取得、デフォルトは1) $numLinksAroundCurrent = 2; // 現在のページの前後で表示するページ番号の数 (例: 2なら [N-2, N-1, N, N+1, N+2]) // --- 計算される値 --- $totalPages = ceil($totalItems / $itemsPerPage); // 総ページ数 // 現在のページ番号が有効範囲内かチェック(念のため) if ($currentPage < 1) { $currentPage = 1; } elseif ($currentPage > $totalPages) { $currentPage = $totalPages; } // データベースクエリのためのオフセット計算 (例: LIMIT $itemsPerPage OFFSET $offset) $offset = ($currentPage - 1) * $itemsPerPage; // ここでデータベースから `$itemsPerPage` 件のデータを `$offset` から取得する処理が入る // 例: SELECT * FROM articles LIMIT :itemsPerPage OFFSET :offset // ... echo "総アイテム数: " . $totalItems . "<br>"; echo "1ページあたりの表示数: " . $itemsPerPage . "<br>"; echo "現在のページ: " . $currentPage . "<br>"; echo "総ページ数: " . $totalPages . "<br>"; echo "オフセット: " . $offset . "<br><br>"; // ページネーションのリンクを生成するためのHTMLの開始 echo '<nav aria-label="ページネーション"><ul class="pagination">'; // 「前へ」ボタン if ($currentPage > 1) { echo '<li class="page-item"><a class="page-link" href="?page=' . ($currentPage - 1) . '">前へ</a></li>'; } else { echo '<li class="page-item disabled"><span class="page-link">前へ</span></li>'; } // ここにページ番号のロジックが入る (次のステップで実装) // ... // 「次へ」ボタン if ($currentPage < $totalPages) { echo '<li class="page-item"><a class="page-link" href="?page=' . ($currentPage + 1) . '">次へ</a></li>'; } else { echo '<li class="page-item disabled"><span class="page-link">次へ</span></li>'; } echo '</ul></nav>'; ?>

ステップ2: 省略記号「...」のロジックを組み込む

いよいよ、ページ番号と省略記号を生成するメインロジックです。表示するページ番号の範囲を計算し、必要に応じて「...」を挿入します。

Php
<?php // ... (ステップ1のコードから続く) // ページ番号のリストを格納する配列 $pageLinks = []; // 現在のページを中心とした表示範囲の開始と終了を計算 $startPage = max(1, $currentPage - $numLinksAroundCurrent); $endPage = min($totalPages, $currentPage + $numLinksAroundCurrent); // 1ページ目を常に表示 if ($startPage > 1) { $pageLinks[] = 1; // 1ページ目と現在の表示範囲の間にギャップがあれば「...」を挿入 if ($startPage > 2) { $pageLinks[] = '...'; } } // 現在のページ周辺のページ番号を追加 for ($i = $startPage; $i <= $endPage; $i++) { $pageLinks[] = $i; } // 最後のページを常に表示 if ($endPage < $totalPages) { // 現在の表示範囲と最後のページの間にギャップがあれば「...」を挿入 if ($endPage < $totalPages - 1) { $pageLinks[] = '...'; } $pageLinks[] = $totalPages; } // ページネーションのHTML出力 (ステップ1の「前へ」「次へ」ボタンの間に挿入) foreach ($pageLinks as $link) { if ($link === '...') { echo '<li class="page-item disabled"><span class="page-link">...</span></li>'; } elseif ($link == $currentPage) { echo '<li class="page-item active" aria-current="page"><span class="page-link">' . $link . '</span></li>'; } else { echo '<li class="page-item"><a class="page-link" href="?page=' . $link . '">' . $link . '</a></li>'; } } // ... (ステップ1の「次へ」ボタン以降のコードに続く) ?>

完全なコード例:

Php
<?php // --- 基本情報の設定 --- $totalItems = 1000; // 総アイテム数 (例: データベースの総レコード数) $itemsPerPage = 10; // 1ページあたりの表示アイテム数 $currentPage = isset($_GET['page']) ? (int)$_GET['page'] : 1; // 現在のページ番号 (URLから取得、デフォルトは1) $numLinksAroundCurrent = 2; // 現在のページの前後で表示するページ番号の数 (例: 2なら [N-2, N-1, N, N+1, N+2]) // --- 計算される値 --- $totalPages = ceil($totalItems / $itemsPerPage); // 総ページ数 // 現在のページ番号が有効範囲内かチェック if ($currentPage < 1) { $currentPage = 1; } elseif ($currentPage > $totalPages) { $currentPage = $totalPages; } // データベースクエリのためのオフセット計算 (例: LIMIT $itemsPerPage OFFSET $offset) $offset = ($currentPage - 1) * $itemsPerPage; // ここでデータベースから `$itemsPerPage` 件のデータを `$offset` から取得する処理が入る // 例: SELECT * FROM articles LIMIT :itemsPerPage OFFSET :offset // ... ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PHP ページネーション省略記号</title> <style> /* 簡単なCSS(Bootstrap風を想定) */ .pagination { display: flex; list-style: none; padding: 0; margin: 20px 0; } .page-item { margin: 0 2px; } .page-link { display: block; padding: 8px 12px; text-decoration: none; color: #007bff; border: 1px solid #dee2e6; border-radius: 4px; transition: all 0.2s; } .page-link:hover { background-color: #e9ecef; } .page-item.active .page-link { background-color: #007bff; color: white; border-color: #007bff; } .page-item.disabled .page-link { color: #6c757d; pointer-events: none; background-color: #f8f9fa; } .page-item.active .page-link:hover { cursor: default; } /* アクティブページのホバーは無効 */ </style> </head> <body> <h1>PHP ページネーションの例</h1> <p>現在のページ: <?php echo $currentPage; ?> / <?php echo $totalPages; ?></p> <p>総アイテム数: <?php echo $totalItems; ?></p> <p>1ページあたりの表示数: <?php echo $itemsPerPage; ?></p> <p>オフセット (DBクエリ用): <?php echo $offset; ?></p> <nav aria-label="ページネーション"> <ul class="pagination"> <?php // 「前へ」ボタン if ($currentPage > 1) { echo '<li class="page-item"><a class="page-link" href="?page=' . ($currentPage - 1) . '">前へ</a></li>'; } else { echo '<li class="page-item disabled"><span class="page-link">前へ</span></li>'; } // ページ番号のリストを格納する配列 $pageLinks = []; // 現在のページを中心とした表示範囲の開始と終了を計算 $startPage = max(1, $currentPage - $numLinksAroundCurrent); $endPage = min($totalPages, $currentPage + $numLinksAroundCurrent); // 1ページ目を常に表示 if ($startPage > 1) { $pageLinks[] = 1; // 1ページ目と現在の表示範囲の間にギャップがあれば「...」を挿入 if ($startPage > 2) { // 例: [1] ... [5] [6] [7] $pageLinks[] = '...'; } } // 現在のページ周辺のページ番号を追加 for ($i = $startPage; $i <= $endPage; $i++) { $pageLinks[] = $i; } // 最後のページを常に表示 if ($endPage < $totalPages) { // 現在の表示範囲と最後のページの間にギャップがあれば「...」を挿入 if ($endPage < $totalPages - 1) { // 例: [3] [4] [5] ... [100] $pageLinks[] = '...'; } $pageLinks[] = $totalPages; } // ページネーションのHTML出力 foreach ($pageLinks as $link) { if ($link === '...') { echo '<li class="page-item disabled"><span class="page-link">...</span></li>'; } elseif ($link == $currentPage) { echo '<li class="page-item active" aria-current="page"><span class="page-link">' . $link . '</span></li>'; } else { echo '<li class="page-item"><a class="page-link" href="?page=' . $link . '">' . $link . '</a></li>'; } } // 「次へ」ボタン if ($currentPage < $totalPages) { echo '<li class="page-item"><a class="page-link" href="?page=' . ($currentPage + 1) . '">次へ</a></li>'; } else { echo '<li class="page-item disabled"><span class="page-link">次へ</span></li>'; } ?> </ul> </nav> </body> </html>

このコードを実行し、URLの?page=パラメータを様々に変更してみてください。例えば、?page=1?page=5?page=90?page=99などの値で、ページネーションの表示がどのように変化するかを確認できます。

ハマった点やエラー解決

1. 「...」の表示条件が複雑になりがち

  • 問題: ... を表示する条件を正確に設定しないと、... が多重に表示されたり、本来表示されるべきところで表示されなかったりします。特に、現在のページが最初や最後のページに近い場合の境界条件の考慮が漏れがちです。
  • 解決策:
    • if ($startPage > 2)if ($endPage < $totalPages - 1) のように、... を挿入する条件に 1totalPages との距離を考慮に入れることで、冗長な ... の表示を防ぎ、正しい位置にのみ挿入できます。
    • まず、表示したい全てのページ番号(1、currentPage周辺、totalPages)を配列に入れ、その後ソート・重複排除を行い、隣り合う番号の間にギャップがあれば ... を挿入するという別のアプローチも考えられます。本記事では、より直感的に範囲を計算し、条件分岐で ... を挿入する方式を採用しました。

2. URLパラメータの処理と型変換

  • 問題: URLから取得する$_GET['page']は文字列なので、そのまま計算に使うと予期せぬ挙動を引き起こす可能性があります。また、不正な値(負の数、非数値、大きすぎる値)が渡された場合のハンドリングも必要です。
  • 解決策:
    • (int)$_GET['page'] のように明示的に整数型にキャストすることで、数値としての計算を安全に行えます。
    • max(1, $currentPage)min($totalPages, $currentPage) を使って、計算された$currentPageが常に1以上$totalPages以下の範囲に収まるようにバリデーションを行います。

3. CSSでのスタイリングの考慮

  • 問題: PHPのロジックはページ番号を生成しますが、それがどのように表示されるかはCSSに依存します。特にアクティブなページや無効なボタン(「前へ」の1ページ目など)のスタイルが重要です。
  • 解決策:
    • activedisabled といったクラス名をHTML要素に付与することで、CSSでこれらの状態を視覚的に区別できます。本記事の例では、簡単なCSSを提供していますが、Bootstrapのようなフレームワークを利用すると、より手軽にプロフェッショナルな見た目を実現できます。

まとめ

本記事では、PHPでページネーションに「...」の省略記号を追加し、ユーザーエクスペリエンスを向上させる方法を具体的に解説しました。

  • 多数のページがある場合にUIをシンプルに保つために、省略記号が極めて重要であることを理解しました。
  • 現在のページを中心に表示範囲を決定し、最初のページ(1)と最後のページ(総ページ数)は常に表示するというページネーションのデザイン原則を学びました。
  • PHPの条件分岐とループを駆使し、動的にページ番号と省略記号を生成する具体的なロジックとコードを実装しました。

この記事を通して、あなたはユーザビリティの高い、洗練されたページネーションをPHPで実装できるようになります。これにより、ユーザーはWebアプリケーションをより快適に利用できるようになり、離脱率の低下やエンゲージメントの向上にもつながるでしょう。

今後は、AJAXを利用した非同期ページネーションや、LaravelなどのPHPフレームワークにおけるページネーションの効率的な実装方法についても記事にする予定です。

参考資料