解説コードを読んでみよう
このサンプルでわかること
- 最初の25件だけを PHP で表示し、続きは ajax で読み込む方法
- 「何件目から読むか」を送り、続きのデータを JSON で返す PHP の作り方
- 残りがなくなったらボタンを消す方法
使っているファイル
- index.phpデモのページ(最初の25件を表示)
- more.phpajax で呼ばれ、続きの25件を JSON で返す
- data.php見本のデータ(お知らせ100件)と、取り出す関数
1. データと、取り出す関数
見本のお知らせを100件作ります。get_page($offset) は「$offset 件目から25件」を取り出す関数です。array_slice() は、配列の一部を切り出す関数です。
<?php
// 見本のデータ:お知らせ 100 件
// 本物のサービスでは、ここでデータベースから取り出します(解説の最後を参照)
const PER_PAGE = 25; // 1回に読み込む件数
// お知らせを新しい順に 100 件作る
function get_notices(): array
{
static $list = null; // 一度作ったら使い回す
if ($list !== null) {
return $list;
}
$cats = ['お知らせ', 'イベント', 'メンテナンス', 'キャンペーン'];
$last = new DateTimeImmutable('2026-10-31');
$list = [];
for ($i = 100; $i >= 1; $i--) {
$list[] = [
'id' => $i,
'date' => $last->modify('-' . ((100 - $i) * 2) . ' days')->format('Y-m-d'),
'cat' => $cats[$i % 4],
'title' => sprintf('サンプルのお知らせ その%d', $i),
];
}
return $list;
}
// $offset 件目から $limit 件を取り出す(SQL の LIMIT と同じ働き)
function get_page(int $offset, int $limit = PER_PAGE): array
{
return array_slice(get_notices(), $offset, $limit);
}
// 全部の件数
function count_notices(): int
{
return count(get_notices());
}
function h($s): string
{
return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
}
2. 最初の25件を PHP で表示する
ページを開いた時点で、最初の25件は PHP が表にして送ります。ボタンには「次は何件目から読むか」を data-next に書いておきます。
<table class="table table-striped notice-table">
<thead><tr><th>No.</th><th>日付</th><th>種類</th><th>件名</th></tr></thead>
<tbody id="rows">
<?php foreach ($first as $row): ?>
<tr><td><?= h($row['id']) ?></td><td><?= h($row['date']) ?></td><td><?= h($row['cat']) ?></td><td><?= h($row['title']) ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="more-wrap">
<button type="button" class="btn btn-primary" id="more" data-next="<?= count($first) ?>">次の25件を読み込む</button>
3. ボタンを押したら、続きを読み込む
$.getJSON() で more.php を呼び、offset(何件目から)を送ります。返ってきたデータを1件ずつ <tr> にして、表の最後に付け足します。
$("#more").on("click", function(){
var $btn = $(this);
$btn.prop("disabled", true).text("読み込み中…");
// more.php に「何件目から」を送り、続きの25件を JSON で受け取る
$.getJSON("more.php", { offset: $btn.data("next") })
.done(function(res){
$.each(res.items, function(i, row){
$("<tr>").append(
$("<td>").text(row.id),
$("<td>").text(row.date),
$("<td>").text(row.cat),
$("<td>").text(row.title)
).appendTo("#rows");
});
$("#shown").text($("#rows tr").length);
if (res.next === null) { // 残りがなければボタンを消す
$btn.prop("hidden", true);
$("#end").prop("hidden", false);
} else { // 次に読む位置を覚えておく
$btn.data("next", res.next).prop("disabled", false).text("次の25件を読み込む");
}
})
.fail(function(){
$btn.prop("disabled", false).text("もう一度読み込む");
});
});
});
読み込んでいるあいだはボタンを disabled(押せない状態)にしています。続けて何回も押されて、同じ25件が二重に付け足されるのを防ぐためです。
4. more.php で、続きの25件を返す
<?php
// 「次の25件」を JSON で返す
require_once __DIR__ . '/data.php';
header('Content-Type: application/json; charset=UTF-8');
// 何件目から読むか(0 以上の整数でなければ 0 にする)
$offset = filter_var($_GET['offset'] ?? 0, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
if ($offset === false) {
$offset = 0;
}
$items = get_page($offset);
$next = $offset + count($items);
echo json_encode([
'items' => $items,
'next' => $next < count_notices() ? $next : null, // 残りがなければ null
], JSON_UNESCAPED_UNICODE);
次に読む位置(next)も一緒に返します。もう残りがなければ null を返し、JavaScript はそれを見てボタンを消します。
| 押した回数 | 送る offset | 返ってくる件 | next |
|---|---|---|---|
| 1回目 | 25 | 26〜50件目 | 50 |
| 2回目 | 50 | 51〜75件目 | 75 |
| 3回目 | 75 | 76〜100件目 | null(おしまい) |
データベースを使うときは
このページは見本なので、データベースを使わずに動かしています。本物のサービスにするときは、データを取り出す関数の中身だけをデータベースの読み書きに取り替えます。画面の作り方(HTML と JavaScript)は同じままで使えます。
get_page() の中を SQL の LIMIT(何件)と OFFSET(何件目から)に置き換えます。数字は必ず bindValue() で整数として渡します。
function get_page(int $offset, int $limit = PER_PAGE): array
{
$stmt = db()->prepare('SELECT id, date, cat, title FROM notices ORDER BY id DESC LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}