htmx 逆引きレシピ
定期ポーリングで更新するには?

公開日:
最終更新日:

「今の進捗どうなった?」や「監視画面を更新したい」を、ページ全体リロードなしで実現したいときは 定期ポーリングが便利です。
htmxなら hx-trigger="every 2s" のように書くだけで、一定間隔で最新HTMLを取りにいけます。

このページでは、進捗の定期更新監視ダッシュボードリアルタイム風の新着追加の3パターンを紹介します。
必要な部分だけを差し替えるので、軽く・分かりやすく運用できます。

使用するhtmx属性

  • hx-trigger:リクエストを発火するタイミングを指定(例:load, every 2s
  • hx-get:GETで更新用のHTMLを取りに行って差し替える(定期更新と相性が良い)
  • hx-target:返ってきたHTMLを差し替える先の要素(CSSセレクタ)を指定
  • hx-swap:差し替え方を指定(例:innerHTML / afterbegin

利用シーン

  • 「進捗/ステータスを定期更新」:バッチや非同期処理の進捗を、画面を更新せず追いかけたい
  • 「監視ダッシュボード」:指標カードやログを一定間隔で更新して、異常に早く気づきたい
  • 「手軽にリアルタイム風」:新着通知や更新履歴を、自然に増えていくUIで見せたい

デモ用CSS

CSS

<style>
		.CARD{ padding: 1rem; border: 1px solid rgba(0,0,0,.12); border-radius: .8rem; background:#fff; }
		.HR{ margin: 1.25rem 0; border: 0; border-top: 1px dashed rgba(0,0,0,.18); }
		.HTMX-NOTE{ opacity: .78; font-size: .95rem; }
		code{ background: rgba(0,0,0,.05); padding: .1rem .35rem; border-radius: .35rem; }

		.POLL-TRIGGER{ display:none; }

		.PROGRESS{
			height: 12px;
			border-radius: 999px;
			background: rgba(0,0,0,.08);
			overflow: hidden;
		}
		.PROGRESS__BAR{
			height: 100%;
			width: var(--P, 0%);
			background: rgba(21,101,192,.85);
		}
		.TAG{
			display:inline-block;
			padding:.15rem .55rem;
			border-radius:999px;
			background:rgba(0,0,0,.06);
			font-size:.85rem;
		}

		.GRID{
			display:grid;
			grid-template-columns: repeat(3, minmax(0, 1fr));
			gap:.75rem;
		}
		@media (max-width: 780px){
			.GRID{ grid-template-columns: 1fr; }
		}

		table{ width:100%; border-collapse:collapse; }
		th,td{ padding:.55rem .6rem; border-bottom:1px solid rgba(0,0,0,.08); vertical-align:top; }
		th{ background: rgba(0,0,0,.03); text-align:left; white-space:nowrap; }

		.FEED{
			display:grid;
			gap:.5rem;
			max-height: 260px;
			overflow:auto;
			padding-right:.25rem;
		}
		.FEED li{
			list-style:none;
			border:1px solid rgba(0,0,0,.10);
			border-radius:.75rem;
			padding:.55rem .75rem;
			background: rgba(0,0,0,.02);
		}
	</style>

① 進捗/ステータスを定期更新

バックグラウンド処理の進捗(%)や状態を、一定間隔で自動更新するデモです。
「完了したか分からない不安」を減らし、待ち時間をストレスなく見せられます。

HTML

<div class="DEMO">

	<h4>① 進捗/ステータスを定期更新</h4>

	<div
		class="POLL-TRIGGER"
		hx-get="/htmx/demo/_poll_progress.php"
		hx-trigger="load, every 2s"
		hx-target="#DEMO_POLL_1_RESULT"
		hx-swap="innerHTML"
		aria-hidden="true"
	></div>

	<div id="DEMO_POLL_1_RESULT" class="CARD">
		<p class="HTMX-NOTE">ここに進捗が表示されます(自動更新)。</p>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// セッションを開始する(進捗を保持する)
session_start();

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// 進捗データが無ければ初期化する
if (!isset($_SESSION['demo_poll_progress']) || !is_array($_SESSION['demo_poll_progress'])) {

	// 初期の進捗状態を作る
	$_SESSION['demo_poll_progress'] = [
		'job_id'   => 12001,
		'percent'  => 0,
		'status'   => 'RUNNING',
		'message'  => '処理を開始しました。',
		'updated'  => date('H:i:s'),
	];
}

// 進捗データを取り出す
$st = (array)$_SESSION['demo_poll_progress'];

// 現在の進捗率を取り出す
$percent = (int)($st['percent'] ?? 0);

// 現在のステータスを取り出す
$status = (string)($st['status'] ?? 'RUNNING');

// 進捗が完了していたらリセットする
if ($percent >= 100) {

	// 次のジョブIDに進める
	$st['job_id'] = (int)($st['job_id'] ?? 0) + 1;

	// 進捗をゼロに戻す
	$st['percent'] = 0;

	// ステータスを実行中に戻す
	$st['status'] = 'RUNNING';

	// メッセージを更新する
	$st['message'] = '新しいジョブを開始しました。';
}

// 進捗を少し進める(ランダム)
$delta = random_int(3, 14);

// 進捗を増やす
$st['percent'] = min(100, (int)($st['percent'] ?? 0) + $delta);

// 進捗率を取り直す
$percent = (int)$st['percent'];

// ステータスを進捗に応じて変える
if ($percent >= 100) {

	// 完了状態にする
	$st['status'] = 'DONE';

	// 完了メッセージにする
	$st['message'] = '完了しました。';
} elseif ($percent >= 70) {

	// 後半メッセージにする
	$st['message'] = '最終処理中です。';
} elseif ($percent >= 30) {

	// 中盤メッセージにする
	$st['message'] = 'データを処理しています。';
} else {

	// 前半メッセージにする
	$st['message'] = '準備中です。';
}

// 更新時刻を入れる
$st['updated'] = date('H:i:s');

// セッションへ保存する
$_SESSION['demo_poll_progress'] = $st;

// 表示用の色ラベルを決める
$tag = $st['status'] === 'DONE' ? '完了' : '処理中';

// 進捗カードを描画する
echo '<div style="display:grid; gap:.6rem;">';

// タイトル行を描画する
echo '<div><strong>ジョブ #' . h((string)$st['job_id']) . '</strong> <span class="TAG">' . h($tag) . '</span></div>';

// メッセージを描画する
echo '<div class="HTMX-NOTE">' . h((string)$st['message']) . '</div>';

// 進捗率を描画する
echo '<div><strong>' . h((string)$percent) . '%</strong> <span class="HTMX-NOTE">(更新:' . h((string)$st['updated']) . ')</span></div>';

// 進捗バーを描画する
echo '<div class="PROGRESS"><div class="PROGRESS__BAR" style="--P:' . h((string)$percent) . '%;"></div></div>';

// 閉じる
echo '</div>';

デモ

① 進捗/ステータスを定期更新

ここに進捗が表示されます(自動更新)。

解説

  • hx-trigger="load, every 2s" で、ページ表示直後から2秒ごとに更新を行います。
    ユーザー操作なしで自動更新できるため、進捗表示や監視画面に向いています。
  • hx-target を進捗エリアに絞ることで、必要な範囲だけが差し替わるようになります。
    画面全体を更新しないので、レイアウトの揺れが起きにくく体感も軽くなります。
  • サーバ側では進捗を保持し、毎回「今の状態」を返すだけにしています。
    定期更新は“最後の状態を取りに行く”設計にすると、画面と状態がズレにくくなります。

② 監視ダッシュボード

キュー数・エラー数・応答時間などの指標と、直近ログをまとめて監視するデモです。
監視画面を開いているだけで最新状態になるので、異常の早期発見に役立ちます。

HTML

<div class="DEMO">

	<h4>② 監視ダッシュボード</h4>

	<div
		class="POLL-TRIGGER"
		hx-get="/htmx/demo/_poll_dashboard.php"
		hx-trigger="load, every 5s"
		hx-target="#DEMO_POLL_2_RESULT"
		hx-swap="innerHTML"
		aria-hidden="true"
	></div>

	<div id="DEMO_POLL_2_RESULT" class="CARD">
		<p class="HTMX-NOTE">ここにダッシュボードが表示されます(自動更新)。</p>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// セッションを開始する(指標とログを保持する)
session_start();

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// ダッシュボード状態が無ければ初期化する
if (!isset($_SESSION['demo_poll_dashboard']) || !is_array($_SESSION['demo_poll_dashboard'])) {

	// 初期状態を作る
	$_SESSION['demo_poll_dashboard'] = [
		'queue'   => 12,
		'errors'  => 0,
		'latency' => 180,
		'logs'    => [],
		'updated' => date('H:i:s'),
	];
}

// 状態を取り出す
$st = (array)$_SESSION['demo_poll_dashboard'];

// 指標を少し変化させる
$st['queue'] = max(0, (int)($st['queue'] ?? 0) + random_int(-3, 6));

// エラー数を少し変化させる
$st['errors'] = max(0, (int)($st['errors'] ?? 0) + random_int(-1, 2));

// 平均応答(ms)を少し変化させる
$st['latency'] = max(40, (int)($st['latency'] ?? 0) + random_int(-30, 45));

// 更新時刻を入れる
$st['updated'] = date('H:i:s');

// ログ配列を取り出す
$logs = (array)($st['logs'] ?? []);

// ログの種類を用意する
$types = ['INFO', 'WARN', 'ERROR'];

// ログの本文候補を用意する
$msgs = [
	'キューを処理しました。',
	'バッチ処理が開始されました。',
	'外部APIが遅延しています。',
	'リトライを実行しました。',
	'処理が完了しました。',
	'一部のレコードで検証エラーが発生しました。',
];

// ログを1件追加する
$logs[] = [
	'time' => date('H:i:s'),
	'type' => $types[random_int(0, count($types) - 1)],
	'msg'  => $msgs[random_int(0, count($msgs) - 1)],
];

// ログを最大8件に制限する
$logs = array_slice($logs, -8);

// 状態へ戻す
$st['logs'] = $logs;

// セッションへ保存する
$_SESSION['demo_poll_dashboard'] = $st;

// カード表示を描画する
echo '<div style="display:grid; gap:.9rem;">';

// 見出し行を描画する
echo '<div><strong>監視ダッシュボード</strong> <span class="HTMX-NOTE">(更新:' . h((string)$st['updated']) . ')</span></div>';

// 指標カードのグリッドを描画する
echo '<div class="GRID">';

// キュー数カードを描画する
echo '<div class="CARD" style="background:rgba(0,0,0,.02);">';
echo '<div class="HTMX-NOTE">待ちキュー</div>';
echo '<div style="font-size:1.35rem;"><strong>' . h((string)$st['queue']) . '</strong> 件</div>';
echo '</div>';

// エラー数カードを描画する
echo '<div class="CARD" style="background:rgba(0,0,0,.02);">';
echo '<div class="HTMX-NOTE">直近エラー</div>';
echo '<div style="font-size:1.35rem;"><strong>' . h((string)$st['errors']) . '</strong> 件</div>';
echo '</div>';

// 応答時間カードを描画する
echo '<div class="CARD" style="background:rgba(0,0,0,.02);">';
echo '<div class="HTMX-NOTE">平均応答</div>';
echo '<div style="font-size:1.35rem;"><strong>' . h((string)$st['latency']) . '</strong> ms</div>';
echo '</div>';

// グリッドを閉じる
echo '</div>';

// ログ表を描画する
echo '<table>';
echo '<thead><tr><th>時刻</th><th>種別</th><th>内容</th></tr></thead>';
echo '<tbody>';

// ログを上から描画する(新しい順に見せたいので逆順)
foreach (array_reverse($logs) as $r) {

	// 行を描画する
	echo '<tr>';
	echo '<td>' . h((string)($r['time'] ?? '')) . '</td>';
	echo '<td><span class="TAG">' . h((string)($r['type'] ?? '')) . '</span></td>';
	echo '<td>' . h((string)($r['msg'] ?? '')) . '</td>';
	echo '</tr>';
}

// tbodyを閉じる
echo '</tbody>';

// tableを閉じる
echo '</table>';

// 閉じる
echo '</div>';

デモ

② 監視ダッシュボード

ここにダッシュボードが表示されます(自動更新)。

解説

  • ダッシュボード全体を hx-get で取得し、結果エリアに innerHTML で差し替えています。
    カード+ログのように “まとまり” があるUIは、まとめて更新した方が整合性が保てます。
  • 更新間隔を少し長め(例:5秒)にすることで、サーバ負荷や通信量を抑えやすくなります。
    監視の目的に合わせて「どれくらいの新しさが必要か」を決めるのがコツです。
  • 「指標の数値が変わった」「ログが増えた」など、変化が目に見える構成にすると“動いてる感”が出ます。
    定期更新は、数値や履歴が変化するUIと相性がとても良いです。

③ 手軽にリアルタイム風(新着イベントを追加)

新着イベントを定期的に取得し、一覧の先頭に追加して“リアルタイム風”に見せるデモです。
通知・更新履歴・監査ログなどを、自然に流れてくるように表現できます。

HTML

<div class="DEMO">

	<h4>③ 手軽にリアルタイム風(新着イベントを追加)</h4>

	<div
		class="POLL-TRIGGER"
		hx-get="/htmx/demo/_poll_realtime_feed.php"
		hx-trigger="load, every 2s"
		hx-target="#DEMO_POLL_3_FEED"
		hx-swap="afterbegin"
		aria-hidden="true"
	></div>

	<div class="CARD">
		<ul id="DEMO_POLL_3_FEED" class="FEED">
			<li class="HTMX-NOTE">ここに新着イベントが追加されます(自動更新)。</li>
		</ul>
	</div>

</div>

PHP

<?php
// 型を厳密に扱う
declare(strict_types=1);

// セッションを開始する(連番を保持する)
session_start();

// HTMLとして返す
header('Content-Type: text/html; charset=UTF-8');

// HTMLエスケープ関数を用意する
function h(string $s): string {
	// 特殊文字をエスケープする
	return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}

// フィード状態が無ければ初期化する
if (!isset($_SESSION['demo_poll_feed']) || !is_array($_SESSION['demo_poll_feed'])) {

	// 初期状態を作る
	$_SESSION['demo_poll_feed'] = [
		'seq' => 9000,
	];
}

// 状態を取り出す
$st = (array)$_SESSION['demo_poll_feed'];

// 連番を進める
$st['seq'] = (int)($st['seq'] ?? 0) + 1;

// セッションへ保存する
$_SESSION['demo_poll_feed'] = $st;

// イベント種別を用意する
$types = ['作成', '更新', '承認', '差し戻し', '削除'];

// イベント本文候補を用意する
$msgs = [
	'申請を作成しました。',
	'担当者を変更しました。',
	'ステータスを更新しました。',
	'コメントが追加されました。',
	'明細が修正されました。',
];

// 種別を選ぶ
$type = $types[random_int(0, count($types) - 1)];

// 本文を選ぶ
$msg = $msgs[random_int(0, count($msgs) - 1)];

// 表示を返す(liだけ返してafterbeginで追加される)
echo '<li>';
echo '<div><strong>#' . h((string)$st['seq']) . '</strong> <span class="TAG">' . h($type) . '</span></div>';
echo '<div class="HTMX-NOTE">' . h($msg) . '(' . h(date('H:i:s')) . ')</div>';
echo '</li>';

デモ

③ 手軽にリアルタイム風(新着イベントを追加)

  • ここに新着イベントが追加されます(自動更新)。

解説

  • hx-swap="afterbegin" を使うことで、新しい要素を先頭に“積み上げる”表示にできます。
    差し替えではなく追加になるため、「新着が増えている」印象が直感的に伝わります。
  • 返却するHTMLは <li> など最小単位にすると、描画が軽くなります。
    リアルタイム風表示は回数が増えやすいので、“小さく返す”設計が安心です。
  • 表示が増え続けるケースでは、スクロール領域にする・件数を絞るなどの工夫が有効です。
    運用時は「増えすぎ対策」まで考えておくと、実務でもそのまま使いやすくなります。

次に読むオススメレシピ

このページの著者

もちもちみかん(システムエンジニア)

社内SEとしてグループ企業向けの業務アプリを要件定義〜運用まで一気通貫で担当しています。

経験:Webアプリ/業務システム

得意:PHP・JavaScript・MySQL・CSS

個人実績:フォーム生成基盤クイズ学習プラットフォーム

詳しいプロフィールはこちら!  もちもちみかんのプロフィール

もちもちみかん0系くん
TOPへ

もちもちみかん.comとは


このサイトでは、コーディングがめんどうくさい人向けのお助けツールとして、フォームやCSSをノーコードで生成できる、
 もちもちみかん.forms
 もちもちみかん.css1
 もちもちみかん.css2
と言ったジェネレーターを用意してます。

また、このサイトを通じて、「もちもちみかん」のかわいさを普及したいとかんがえてます!