ぬまろぐ

←戻る

Astroでページング画面(ページネーション)を作る方法

2023/06/04

Astroでページング画面(ページネーション)を作る方法を紹介します。ページング用の機能は用意されており、公式サイトにも紹介はされていましたので、簡単に作ることができました。自作のページリンク用のコンポーネントもつけておきます。

ページネーションのイメージ

ブログ一覧のページの下部などにページ番号を表示して、クリックすることで次のページなどへ飛ぶことができる機能です。私の場合は1ページのブログ数は12個にしているため、それを超えるとページ番号が表示されるようになります。

IMG

ページネーションの作成方法

ファイル名に[page]を付ける

まず、ブログ一覧などを表示するページのファイル名に”[page]”を付けます。

私は、article-[page].astroというファイル名にしました。astroをビルドした時に、ここの[page]部分がページ番号になります。

atricle-1, article-2…

pagenateの利用

次にarticle-[page].astroのフロントマターで、getStaticPathsにpaginateを渡し、getStaticPaths内でpaginateをreturnするようにします。

以下のコードはmdファイル一覧を取得して、一覧ページで12個ごとのページリンクにしているサンプルです。

astroが用意するのは、これでビルドした時に、article-1には1~12までの記事、article-2には13~24までの記事といったページを生成するところまで。ページ番号の表示やリンクは自分で作る必要があります。

---
import Layout from "../layouts/Layout.astro";
import Paging from '../components/Paging.astro';
export async function getStaticPaths({ paginate }) {
  const allPosts = await Astro.glob("../pages/posts/*.md");
  allPosts.sort((a, b) => Date.parse(b.frontmatter.date) - Date.parse(a.frontmatter.date)); //記事を日付でソート
  return paginate(allPosts, { pageSize: 12 }); // ページあたりの記事数を設定
}
const { page } = Astro.props;
---

<Layout title="ぬまろぐ">
  <ul role="list" class="blog-card-grid">
    {
      page.data.map((post) => (
        <a class="blog-card" href={post.url}>
          <article >
              <img
                class="eyecatch"
                alt=""
                src={post.frontmatter.eyecatch}
              />
            <h2>{post.frontmatter.title}</h2>
            <p>{post.frontmatter.date}</p>
            <p>{post.frontmatter.description.slice(0,35)}...</p>
          </article>
        </a>
      ))
    }
  </ul>
  <p>
		<!-- ページとリンクの表示 -->
    <Paging page = {page} name = "blog"/> 
  </p>
</Layout>

ページリンク表示用のコンポーネント

自作ですが、ページリンクを表示するためのコンポーネントを作成しました。これを記事一覧などのページに埋め込み、pageオブジェクトを渡せばリンク化したページ一覧を表示してくれます。

一応スマホサイズとPCサイズで表示を変えるようにレスポンシブ対応もしています。

リンクの生成箇所はサイトにより変わるため適当にカスタマイズして利用ください。

Paging.astro

---
export interface Props {
	page: any;
  name: String;
}
const { page, name, category} = Astro.props;

const pageMaxCount = 3; //最大表示数(前後3つ)
const pageFirst = (page.currentPage - pageMaxCount >= 1) ? page.currentPage - pageMaxCount : 1;
const pageEnd = (page.currentPage + pageMaxCount <= page.lastPage) ?  page.currentPage + pageMaxCount : page.lastPage;
const pageList: any[] = [];
for(let i= pageFirst ; i <= pageEnd; i++ ){
	if( i === page.currentPage ){
		pageList.push({page: i, current:true});
	}else{
		pageList.push({page: i, current:false});
	}
};

---
<div class="pagination">
	<ul class="pagination__list">
	{
		pageList.map((pageInfo) => (
			<div>
			{pageInfo.current ? 
				(<li class="pagination__item--current">{pageInfo.page}</li>):
				(<li class="pagination__item"><a href={import.meta.env.BASE_URL + name + "-" + pageInfo.page}>{pageInfo.page}</a></li>)
			}
			</div>
		))
	}
	</ul>

	<p class="pagination__pos">Page {page.currentPage}/{page.lastPage}</p>
	{
		page.currentPage != 1 ?
		(<a class="pagination__btn--first" href={import.meta.env.BASE_URL + name + "-1"}>&lt;&lt;</a>
		 <a class="pagination__btn--prev" href={import.meta.env.BASE_URL + name + "-" + (page.currentPage-1)}>&lt;</a>):
		(<div />)
	}
	{
		page.currentPage != page.lastPage ?
		(<a class="pagination__btn--next" href={import.meta.env.BASE_URL + name + "-" + (page.currentPage+1)}>&gt;</a>
		 <a class="pagination__btn--last" href={import.meta.env.BASE_URL + name + "-" + page.lastPage}>&gt;&gt;</a>):
		(<div />)
	}
	
</div>

<style>
	.pagination__btn--last, .pagination__btn--first, .pagination__btn--next, .pagination__btn--prev, .pagination__item a, .pagination__item--current {
  box-sizing: border-box;
  display: block;
  color: #FFA353;
  text-decoration: none;
  text-align: center;
  background: #FFF;
  border-radius: 50%;
  min-width: 2.4em;
  transition: all 0.2s;
  margin: 0 0.4em 0 0;
  padding: 0.7em;
}

.pagination__btn--last:hover, .pagination__btn--first:hover, .pagination__btn--next:hover, .pagination__btn--prev:hover, .pagination__item a:hover, .pagination__item--current:hover {
  color: #FFF;
  background: #FFA353;
}

.pagination {
  background: #FFF;
  width: fit-content;
  display: flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  border-radius: calc(1.6em);
  margin: 0 auto;
  padding: 0.4em 1em;
}
.pagination__list {
  display: none;
}
.pagination__pos {
  order: 2;
  color: #FFA353;
  margin: 0 1em;
}
.pagination__btn--prev {
  order: 1;
}
.pagination__btn--next {
  order: 3;
}
.pagination__btn--first {
  display: none;
}
.pagination__btn--last {
  display: none;
}
@media (min-width: 520px) {
  .pagination__list {
    order: 2;
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .pagination__item {
    margin: 0;
  }
  .pagination__item--current {
    background: #FFA353;
    color: #FFF;
    font-weight: bold;
    margin: 0 0.4em 0 0;
  }
  .pagination__pos {
    display: none;
  }
}
@media (min-width: 960px) {
  .pagination .pagination__list {
    order: 3;
  }
  .pagination .pagination__btn--prev {
    order: 2;
  }
  .pagination .pagination__btn--next {
    order: 4;
  }
  .pagination .pagination__btn--first {
    order: 1;
    display: block;
    width: 2.4em;
    padding-left: 0;
    padding-right: 0;
  }
  .pagination .pagination__btn--last {
    order: 5;
    display: block;
    width: 2.4em;
    padding-left: 0;
    padding-right: 0;
  }
</style>