省略メニュー付きページネーションの実装
目次
こんにちは!@Ryo54388667です!☺️
普段は都内でエンジニアとして業務をしてます!
主にTypeScriptやNext.jsといった技術を触っています。今回は省略メニュー付きページネーションの実装方法を紹介していきます!
実装するもの
#まずは実装するものを見ていただくのが良いのかなと思います。
これは本ブログのトップページで実装済みです。こちらのStorybookのほうが見やすいかもしれません。
様々なブログでも実装されているUIですので、Webを探せば実装方法はたくさん紹介されています。ただ、このような省略メニュー付きのページネーションは珍しいのかなと思い、本記事を書き起こしました。
実装の詳細
#コードだけ教えてくれ!というかたは、下記のファイルをご覧ください!
詳細を説明していきます。
const rate = totalCount / PER_PAGE;
const totalPages = rate < MINIMUM_PAGE_COUNT ? MINIMUM_PAGE_COUNT : Math.ceil(rate);
ここで、rate
は総アイテム数を1ページあたりのアイテム数で割った値です。totalPages
は、rateが1未満の場合は1、そうでなければrateを切り上げた値です。ここでは総ページ数を計算しています。例えば 20(記事数) ÷ 4 (記事数 / page) = 5 (page) となります。
次に、前後のページ範囲を計算します。smallNumRange
は現在のページより前のページ番号のリスト、largeNumRange
は現在のページより後のページ番号のリストです。
const previousPageRange = useMemo(() => Array.from({ length: totalPages + INDEX_OFFSET })
.map((_, index) => index + INDEX_OFFSET)
.slice(SMALL_RANGE_START_OFFSET, currentPage - SMALL_RANGE_END_OFFSET), [currentPage, totalPages])
const nextPageRange = useMemo(() => Array.from({ length: totalPages + INDEX_OFFSET })
.map((_, index) => index + INDEX_OFFSET)
.slice(currentPage + LARGE_RANGE_START_OFFSET, totalPages - LARGE_RANGE_END_OFFSET), [currentPage, totalPages])
なぜ SMALL_RANGE_END_OFFSET, LARGE_RANGE_END_OFFSET が必要なのか?
#previousPageRange とnextPageRangeの計算において、sliceメソッドで -1 (SMALL_RANGE_END_OFFSET)を使用しています。これには以下の理由があります。
slice(SMALL_RANGE_START_OFFSET, currentPage - SMALL_RANGE_END_OFFSET) の currentPage - SMALL_RANGE_END_OFFSET
は、現在のページの1つ前までのページを取得するためです。例えば、現在のページが5の場合、previousPageRangeは 2, 3 になります。(Storybookでいうとココです!)
slice(currentPage + LARGE_RANGE_START_OFFSET, totalPages - LARGE_RANGE_END_OFFSET) は、現在のページの次のページから最後のページの1つ前までのページを取得するためです。例えば、現在のページが5で、総ページ数が10の場合、nextPageRangeは 7, 8, 9 になります。
最初のページボタン
#最初のページ(ページ番号1)
<li>
<PaginationItem pageNumber={1} currentPage={currentPage}>1</PaginationItem>
</li>
前のページボタン
#現在のページが1でない場合、前のページに移動するボタン
<li>
<PaginationItem pageNumber={currentPage - 1} currentPage={currentPage}>
<svg
className="-scale-x-90"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>
</PaginationItem>
</li>
省略記号と小さいページ範囲のMenu
#現在のページが4以上の場合、省略記号と小さいページ範囲のMenuを表示します。
{currentPage >= 4 && (
<li className="relative group mx-6">
<button type="button">
<span>...</span>
</button>
<EllipsisMenu range={smallNumRange} />
</li>
)}
EllipsisMenu
については下記のファイルを参照してください。
前のページボタン
#現在のページが3以上の場合、前のページを表示。
{currentPage >= 3 && (
<li>
<PaginationItem pageNumber={currentPage - 1} currentPage={currentPage}>
{currentPage - 1}
</PaginationItem>
</li>
)}
現在のページボタン
#現在のページが最初と最後のページでない場合、現在のページを表示。
{currentPage !== 1 && currentPage !== totalPages && (
<li>
<PaginationItem pageNumber={currentPage} currentPage={currentPage}>
{currentPage}
</PaginationItem>
</li>
)}
次のページボタン
#現在のページが最後のページでない場合、次のページを表示。
{currentPage < totalPages && currentPage + 1 !== totalPages && (
<li>
<PaginationItem pageNumber={currentPage + 1} currentPage={currentPage}>
{currentPage + 1}
</PaginationItem>
</li>
)}
省略記号と大きなページ範囲のMenu
#現在のページが最後のページの3ページ前以下の場合、省略記号とその後のページ範囲を表示。
{currentPage <= totalPages - 3 && (
<li className="relative group mx-6">
<button type="button">
<span>...</span>
</button>
<EllipsisMenu range={largeNumRange} />
</li>
)}
最後のページボタン
#総ページ数が1でない場合、最後のページを表示します。
{totalPages !== 1 && (
<li>
<PaginationItem pageNumber={totalPages} currentPage={currentPage}>
{totalPages}
</PaginationItem>
</li>
)}
次のページボタン
#現在のページが最後のページでない場合、次のページに移動するボタンを表示。
{currentPage !== totalPages && (
<li>
<PaginationItem pageNumber={currentPage + 1} currentPage={currentPage}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>
</PaginationItem>
</li>
)}
以上です!より良い方法があれば教えてください〜
最後まで読んでいただきありがとうございます!
気ままにつぶやいているので、気軽にフォローをお願いします!🥺