省略メニュー付きページネーションの実装

省略メニュー付きページネーションの実装

こんにちは!@Ryo54388667です!☺️

普段は都内でエンジニアとして業務をしてます!

主にTypeScriptやNext.jsといった技術を触っています。今回は省略メニュー付きページネーションの実装方法を紹介していきます!


実装するもの

#

まずは実装するものを見ていただくのが良いのかなと思います。

これは本ブログのトップページで実装済みです。こちらのStorybookのほうが見やすいかもしれません。

様々なブログでも実装されているUIですので、Webを探せば実装方法はたくさん紹介されています。ただ、このような省略メニュー付きのページネーションは珍しいのかなと思い、本記事を書き起こしました。



実装の詳細

#

コードだけ教えてくれ!というかたは、下記のファイルをご覧ください!


詳細を説明していきます。


Copied!
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は現在のページより後のページ番号のリストです。

Copied!
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)

Copied!
<li>
  <PaginationItem pageNumber={1} currentPage={currentPage}>1</PaginationItem>
</li>

前のページボタン

#

現在のページが1でない場合、前のページに移動するボタン

Copied!
<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を表示します。

Copied!
{currentPage >= 4 && (
  <li className="relative group mx-6">
    <button type="button">
      <span>...</span>
    </button>
    <EllipsisMenu range={smallNumRange} />
  </li>
)}

EllipsisMenuについては下記のファイルを参照してください。


前のページボタン

#

現在のページが3以上の場合、前のページを表示。

Copied!
{currentPage >= 3 && (
  <li>
    <PaginationItem pageNumber={currentPage - 1} currentPage={currentPage}>
      {currentPage - 1}
    </PaginationItem>
  </li>
)}

現在のページボタン

#

現在のページが最初と最後のページでない場合、現在のページを表示。

Copied!
{currentPage !== 1 && currentPage !== totalPages && (
  <li>
    <PaginationItem pageNumber={currentPage} currentPage={currentPage}>
      {currentPage}
    </PaginationItem>
  </li>
)}

次のページボタン

#

現在のページが最後のページでない場合、次のページを表示。

Copied!
{currentPage < totalPages && currentPage + 1 !== totalPages && (
  <li>
    <PaginationItem pageNumber={currentPage + 1} currentPage={currentPage}>
      {currentPage + 1}
    </PaginationItem>
  </li>
)}

省略記号と大きなページ範囲のMenu

#

現在のページが最後のページの3ページ前以下の場合、省略記号とその後のページ範囲を表示。

Copied!
{currentPage <= totalPages - 3 && (
  <li className="relative group mx-6">
    <button type="button">
      <span>...</span>
    </button>
    <EllipsisMenu range={largeNumRange} />
  </li>
)}

最後のページボタン

#

総ページ数が1でない場合、最後のページを表示します。

Copied!
{totalPages !== 1 && (
  <li>
    <PaginationItem pageNumber={totalPages} currentPage={currentPage}>
      {totalPages}
    </PaginationItem>
  </li>
)}

次のページボタン

#

現在のページが最後のページでない場合、次のページに移動するボタンを表示。

Copied!
{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>
)}

以上です!より良い方法があれば教えてください〜

最後まで読んでいただきありがとうございます!

気ままにつぶやいているので、気軽にフォローをお願いします!🥺




GitHub
修正をリクエストする
Post to X