ページネーション
数字とページ送りボタンで構成するページネーション。現在地から離れた番号は「…」に省略され、クリックすると即座にページが切り替わります。
ライブデモ
コード
HTML
<!-- 数字+前後+省略(…)のページネーション。現在ページを強調し、クリックで切り替える -->
<div class="npg-frame">
<div class="npg-stage">
<p class="npg-eyebrow">Kioku Journal — 全89件の記事</p>
<p class="npg-current" id="npgCurrentLabel">3 / 9 ページ</p>
<nav class="npg-nav" aria-label="ページネーション" id="npgNav">
<button class="npg-arrow" type="button" data-nav="prev" aria-label="前のページへ">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"></path></svg>
</button>
<ul class="npg-list" id="npgList"></ul>
<button class="npg-arrow" type="button" data-nav="next" aria-label="次のページへ">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"></path></svg>
</button>
</nav>
</div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #f8f8fb; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }
/* ルート: width:100% + height:100vh。中身は flex の margin:auto で常に中央に収める */
.npg-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #f8f8fb; display: flex; }
.npg-stage { margin: auto; text-align: center; padding: 24px; }
.npg-eyebrow { margin: 0 0 4px; font-size: 12px; font-weight: 700; color: #8b91a5; letter-spacing: .02em; }
.npg-current { margin: 0 0 18px; font-size: 20px; font-weight: 800; color: #1c2033; }
.npg-nav { display: inline-flex; align-items: center; gap: 6px; }
.npg-arrow {
width: 34px; height: 34px; border-radius: 9px; border: 1px solid #e2e4ee;
background: #fff; color: #4b4f63; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .18s ease, border-color .18s ease, opacity .18s ease;
}
.npg-arrow svg { width: 15px; height: 15px; }
.npg-arrow svg path { fill: none; stroke: currentColor; stroke-width: 2.3; stroke-linecap: round; stroke-linejoin: round; }
.npg-arrow:hover:not(:disabled) { background: #eef0fb; border-color: #cfd3ec; }
.npg-arrow:disabled { opacity: .35; cursor: not-allowed; }
.npg-list { list-style: none; display: flex; align-items: center; gap: 4px; margin: 0; padding: 0; }
.npg-num {
min-width: 34px; height: 34px; padding: 0 6px; border-radius: 9px; border: 1px solid transparent;
background: none; color: #4b4f63; font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
transition: background .18s ease, color .18s ease, transform .15s ease;
}
.npg-num:hover { background: #eef0fb; color: #4338ca; }
.npg-num.is-current { background: #4338ca; color: #fff; font-weight: 800; transform: translateY(-1px); }
.npg-ellipsis { min-width: 20px; text-align: center; color: #b3b7c8; font-size: 13px; user-select: none; }
@media (max-width: 420px) {
.npg-num { min-width: 30px; height: 30px; font-size: 12.5px; }
.npg-arrow { width: 30px; height: 30px; }
}
@media (prefers-reduced-motion: reduce) {
.npg-num { transition: none; }
}
JavaScript
// クリックでページを切替。現在地から離れたページ番号は「…」に省略する(null安全)
(() => {
const nav = document.getElementById('npgNav');
const list = document.getElementById('npgList');
const label = document.getElementById('npgCurrentLabel');
if (!nav || !list) return;
const TOTAL = 9;
let current = 3;
const pagesToShow = () => {
const set = new Set([1, TOTAL, current, current - 1, current + 1]);
return [...set].filter((p) => p >= 1 && p <= TOTAL).sort((a, b) => a - b);
};
const render = () => {
const pages = pagesToShow();
list.innerHTML = '';
let prev = 0;
pages.forEach((p) => {
if (p - prev > 1) {
const gap = document.createElement('li');
gap.className = 'npg-ellipsis';
gap.textContent = '…';
gap.setAttribute('aria-hidden', 'true');
list.appendChild(gap);
}
const li = document.createElement('li');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'npg-num';
btn.textContent = String(p);
if (p === current) {
btn.classList.add('is-current');
btn.setAttribute('aria-current', 'page');
}
btn.addEventListener('click', () => { current = p; render(); });
li.appendChild(btn);
list.appendChild(li);
prev = p;
});
if (label) label.textContent = `${current} / ${TOTAL} ページ`;
nav.querySelectorAll('[data-nav]').forEach((b) => {
const isPrev = b.dataset.nav === 'prev';
b.disabled = isPrev ? current <= 1 : current >= TOTAL;
});
};
nav.addEventListener('click', (e) => {
const btn = e.target.closest('[data-nav]');
if (!btn) return;
if (btn.dataset.nav === 'prev' && current > 1) current -= 1;
if (btn.dataset.nav === 'next' && current < TOTAL) current += 1;
render();
});
render();
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「ページネーション」の効果を追加してください。
# 追加してほしい効果
ページネーション(ヘッダー & ナビ)
数字とページ送りボタンで構成するページネーション。現在地から離れた番号は「…」に省略され、クリックすると即座にページが切り替わります。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 数字+前後+省略(…)のページネーション。現在ページを強調し、クリックで切り替える -->
<div class="npg-frame">
<div class="npg-stage">
<p class="npg-eyebrow">Kioku Journal — 全89件の記事</p>
<p class="npg-current" id="npgCurrentLabel">3 / 9 ページ</p>
<nav class="npg-nav" aria-label="ページネーション" id="npgNav">
<button class="npg-arrow" type="button" data-nav="prev" aria-label="前のページへ">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"></path></svg>
</button>
<ul class="npg-list" id="npgList"></ul>
<button class="npg-arrow" type="button" data-nav="next" aria-label="次のページへ">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6"></path></svg>
</button>
</nav>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #f8f8fb; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }
/* ルート: width:100% + height:100vh。中身は flex の margin:auto で常に中央に収める */
.npg-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #f8f8fb; display: flex; }
.npg-stage { margin: auto; text-align: center; padding: 24px; }
.npg-eyebrow { margin: 0 0 4px; font-size: 12px; font-weight: 700; color: #8b91a5; letter-spacing: .02em; }
.npg-current { margin: 0 0 18px; font-size: 20px; font-weight: 800; color: #1c2033; }
.npg-nav { display: inline-flex; align-items: center; gap: 6px; }
.npg-arrow {
width: 34px; height: 34px; border-radius: 9px; border: 1px solid #e2e4ee;
background: #fff; color: #4b4f63; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background .18s ease, border-color .18s ease, opacity .18s ease;
}
.npg-arrow svg { width: 15px; height: 15px; }
.npg-arrow svg path { fill: none; stroke: currentColor; stroke-width: 2.3; stroke-linecap: round; stroke-linejoin: round; }
.npg-arrow:hover:not(:disabled) { background: #eef0fb; border-color: #cfd3ec; }
.npg-arrow:disabled { opacity: .35; cursor: not-allowed; }
.npg-list { list-style: none; display: flex; align-items: center; gap: 4px; margin: 0; padding: 0; }
.npg-num {
min-width: 34px; height: 34px; padding: 0 6px; border-radius: 9px; border: 1px solid transparent;
background: none; color: #4b4f63; font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
transition: background .18s ease, color .18s ease, transform .15s ease;
}
.npg-num:hover { background: #eef0fb; color: #4338ca; }
.npg-num.is-current { background: #4338ca; color: #fff; font-weight: 800; transform: translateY(-1px); }
.npg-ellipsis { min-width: 20px; text-align: center; color: #b3b7c8; font-size: 13px; user-select: none; }
@media (max-width: 420px) {
.npg-num { min-width: 30px; height: 30px; font-size: 12.5px; }
.npg-arrow { width: 30px; height: 30px; }
}
@media (prefers-reduced-motion: reduce) {
.npg-num { transition: none; }
}
【JavaScript】
// クリックでページを切替。現在地から離れたページ番号は「…」に省略する(null安全)
(() => {
const nav = document.getElementById('npgNav');
const list = document.getElementById('npgList');
const label = document.getElementById('npgCurrentLabel');
if (!nav || !list) return;
const TOTAL = 9;
let current = 3;
const pagesToShow = () => {
const set = new Set([1, TOTAL, current, current - 1, current + 1]);
return [...set].filter((p) => p >= 1 && p <= TOTAL).sort((a, b) => a - b);
};
const render = () => {
const pages = pagesToShow();
list.innerHTML = '';
let prev = 0;
pages.forEach((p) => {
if (p - prev > 1) {
const gap = document.createElement('li');
gap.className = 'npg-ellipsis';
gap.textContent = '…';
gap.setAttribute('aria-hidden', 'true');
list.appendChild(gap);
}
const li = document.createElement('li');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'npg-num';
btn.textContent = String(p);
if (p === current) {
btn.classList.add('is-current');
btn.setAttribute('aria-current', 'page');
}
btn.addEventListener('click', () => { current = p; render(); });
li.appendChild(btn);
list.appendChild(li);
prev = p;
});
if (label) label.textContent = `${current} / ${TOTAL} ページ`;
nav.querySelectorAll('[data-nav]').forEach((b) => {
const isPrev = b.dataset.nav === 'prev';
b.disabled = isPrev ? current <= 1 : current >= TOTAL;
});
};
nav.addEventListener('click', (e) => {
const btn = e.target.closest('[data-nav]');
if (!btn) return;
if (btn.dataset.nav === 'prev' && current > 1) current -= 1;
if (btn.dataset.nav === 'next' && current < TOTAL) current += 1;
render();
});
render();
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。