ページネーション
前へ/次へと番号ボタンで構成する定番ページャ。ページ数が多いときは「…」で省略し、現在のページを藍色で示す。クリックでページが切り替わる。
プレビュー
操作クリック/タップで動きます。
表示するページ番号は「先頭・末尾・現在地の前後」を集合で作り、飛んだ区間だけ「…」に畳んでいます。現在ページは aria-current="page"、両端では前へ/次へを disabled にします。ページ変更は aria-live の status で読み上げます。
使いどころ
検索結果や一覧ページのように、総件数が多く『特定のページへ直接飛びたい』ニーズがある画面に向く。無限スクロールや『もっと見る』ボタンと違い、番号を見て狙ったページへ一気に移動できるのが利点で、SEOでクロールされる一覧アーカイブとも相性がよい。逆に、SNSのタイムラインのように連続して流し読みする体験や、件数が数十件程度でページが2〜3枚に収まる場合は無限スクロールや単純な『もっと見る』の方が操作数が少なく済む。総ページ数が変動する動的な一覧(フィルタで絞り込むたびに件数が変わる等)では、TOTAL を都度再計算してから pageModel() を作り直す必要がある点もこの実装のまま流用する際の前提になる。
仕組み
ページ番号ボタンをすべて並べるのではなく、pageModel() が『先頭・末尾・現在ページの前後1つずつ』を Set で集めてソートし、番号が1つ以上飛んだ区間だけを null(=…)として差し込むことで省略表示を作っている。render() はこのモデルを毎回 numsEl.textContent = "" から作り直すシンプルな設計で、状態(current)さえ更新すればDOM全体が正しい見た目に再構築される。現在ページのボタンだけクリックイベントを付けず is-current クラスと aria-current="page" を与え、それ以外のボタンには go(n) を仕込む。前へ/次へは current が1またはTOTALのときに disabled を付けるだけで端の処理をしており、ページ送りのたびに render() が prevBtn/nextBtn の disabled も含めて丸ごと再計算する。
実装の注意
このデモはページ番号ボタンにクリックイベントを仕込んでいるだけで、実際のURL(href)には紐づいていない。本番で使う場合は各ページ番号を <a href="?page=3"> のような実リンクにしないと、ブラウザの戻る/進む・ブックマーク・検索エンジンのクロールが機能しなくなる(JSの状態だけで見た目のページが変わる作りは避ける)。もう1つの落とし穴は、render() が numsEl.textContent = "" で全ボタンを毎回作り直していること。ページ番号ボタンをクリックした直後、そのボタン自身がDOMから消えて新しいボタン群に置き換わるため、キーボード操作でフォーカスしていた要素が消滅し、フォーカスがbodyへ飛んでしまう。実装では新しい現在ページのボタンへ明示的に focus() を当て直す一手間が要る。
アクセシビリティ
ページ送り全体を <nav aria-label="ページ送り"> で囲み、現在のページボタンには aria-current="page" を付けている。これによりスクリーンリーダーは『いま何ページ目にいるか』をランドマークとページ番号の両方から把握できる。省略記号(…)はクリックできないただの飾りなので button ではなく span にし aria-hidden="true" を付けて読み上げ・フォーカス対象から外しているのも適切な判断。ページが切り替わるたびに role="status" aria-live="polite" の .pg-status を『N / 12 ページ』へ書き換えており、視覚的なページ番号を目で追えない利用者にもページ移動の結果が伝わる。前述のフォーカス消失の問題を直せば、キーボード操作だけで一覧を隅々まで辿れるナビゲーションになる。
調整して使う
前後に表示するページ数(現在は current±1)を2や3に広げると、ジャンプ先の見通しは良くなるが番号の総数が増えて横幅を圧迫する。スマホ幅では番号ボタンを出さず、常時表示している .pg-status(『1 / 12 ページ』)と前へ/次への矢印だけに絞る構成にすると省スペースになる。先頭・末尾へワンタップで飛べる『最初へ』『最後へ』ボタンを両端に足す構成も、ページ数が数十を超えるアーカイブでは有効。1ページあたりの件数を切り替えるセレクタを添える場合は、件数変更時に current を1へリセットしてから TOTAL を再計算する必要がある。番号ボタンではなく『前へ/次へ』のみのシンプルな構成にする場合も、.pg-status のような現在地の明記は必ず残す。
コード(コピーして使えます)
<div class="pg-wrap">
<p class="pg-status" id="pg-status" role="status" aria-live="polite">1 / 12 ページ</p>
<nav class="pg" aria-label="ページ送り">
<button class="pg-btn pg-arrow" id="pg-prev" aria-label="前のページ" disabled>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><polyline points="9,3 5,7 9,11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<div class="pg-nums" id="pg-nums"></div>
<button class="pg-btn pg-arrow" id="pg-next" aria-label="次のページ">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><polyline points="5,3 9,7 5,11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</nav>
</div>