この記事をおすすめしたい人
- Nuxt.jsでページネーションの作り方が分からない人
- REST APIから取得した場合、最終ページ番号の取得方法が分からない人
- ①②③④⑤みたいなリンクの条件分岐を考えるのがめんどくさい人
- つまりオレ
WordPress Nuxt化の一環です。
今回はページネーション?でいいんだっけ?
「« ①② 3 ④⑤⑥ »」みたいなやつ。
記事一覧とかカテゴリページで次のページやもっと先のページへ移動するためのリンク群を作っていきます。
WordPressの上でな!
※ このブログはまだWordPress製です
このページの目次
まず「ページネーションの作り方」の意味を考える
ヘッドレスCMSと連携するNuxt.jsで、ページネーションの作り方に困るケースは次の4つかなと思います。
- 一覧ページが何ページ生成されるのか計算する方法が分からない
- 次のページへの移動のさせ方が分からない
- 動的にページネーションを生成する方法が分からない
- ページネーションをかっこいいアニメーションさせる方法が分からない
2については後日カテゴリページの作り方として記事を書く予定です。
4については僕には荷が重そうなのでやりません。
今回は1と3について解決します。
一覧ページが何ページ生成されるのかを調べる方法
これは「1ページあたり何記事表示するのか」と「総記事数がいくつあるのか」が分かれば計算できます。
前者は自分で決めるものなので、後者について考えていきます。
仕組み的にはどのCMSでも同じだと思いますが、今回はWordPressに的を絞ります。
カテゴリページの場合
カテゴリページやタグページの場合は非常に簡単です。
/wp-json/wp/v2/categories/1/
上記クエリだと「カテゴリID1のカテゴリ情報」が取得できますが、この中に「count」という値が入っています。
これはそのカテゴリに属する記事がいくつあるのか、という数字です。 つまり楽勝。
注意点としては、「count」は「context=embed」の場合には含まれません。
要素名 | view | embed |
---|---|---|
id | ○ | ○ |
count | ○ | - |
description | ○ | - |
link | ○ | ○ |
name | ○ | ○ |
slug | ○ | ○ |
taxonomy | ○ | ○ |
parent | ○ | - |
meta | ○ | - |
レスポンスサイズの軽量化をしようと「embed」を指定する場合は注意が必要です。 「embed」でも「count」を含めることはできるので、詳しくはカテゴリAPIカスタマイズの記事を見てください。
記事一覧ページの場合
こちらはカテゴリのように簡単にはいきません。
記事APIで一覧を取得した際のレスポンスヘッダに「x-wp-total」という名前で記事総数が入っているのでこれを使います。 詳しくは記事の全件取得の記事を見てもらうとして、コードはこんな↓感じになります。
//APIを取得するURL
const baseUrl = 'https://dev.ore-shika.com'
//リクエスト用のクエリ
const query = baseUrl + '/wp-json/wp/v2/posts'
//最初の10件を取得
const res = await axios.get(query)
//ヘッダから総記事数を取得
const total = res.headers['x-wp-total']
このやり方はカテゴリなどでフィルタをかけた場合↓にも機能します。 リクエストしたクエリに対する記事総数が入っています。
/wp-json/wp/v2/posts?categories=1
なのでこのやり方で1本化してしまっている方が何かと都合がいいかもしれません。
これでカテゴリページでも記事一覧ページでも記事総数を取得できたので、あとは1ページあたりの記事数で割って切り上げると最後のページ番号が計算できます。
//1ページあたりの記事数
const per_page = 10
//記事総数から最後のページを計算
const limit = Math.ceil(res.headers['x-wp-total'] / per_page)
記事総数が52で1ページあたり10記事なら6になります。
ページネーションに必要な機能を考える
動的にページネーションを生成するコードを考える前に、ページネーションに必要な機能を考えてみます。
このサイトのページネーションは「WP-PageNavi」というプラグインを使っているので、これを参考にします。
総ページ数用テキスト
ページネーションが10ページあった場合に「1/10」とか「2/10」と表示する機能です。
前半の現在のページはGETで「?page=2」のように設定予定なのでこれはカテゴリページ作成の記事でやります。
後半の総ページ数は先ほど取得できるようになりました。
前後5ページのナビゲーション
1ページ目にいる場合「①②③④⑤」、5ページ目にいる場合「③④⑤⑥⑦」のように表示されるやつです。
3ページ目までなら1~5、4ページ目以降は現在のページの上下2ページ、みたいな感じで表示します。
条件分岐が多少面倒ですが、まぁやれそうです。
前後のページへのナビゲーション
「«」で前のページへ、「»」で次のページへ移動するやつです。
最初のページで「«」を、最後のページで「»」を非表示にする条件分岐を入れるだけで作れそうです。
ページの省略
6ページ以上ある場合に「①②③④⑤…」と省略を明示するやつです。
これも総ページ数と現在のページが分かれば計算できます。
最初/最後のページへのナビゲーション
総ページ数が多い時に、「« 先頭」「最後 »」などで最初/最後のページへ移動するやつです。
条件分岐を考えるのがめんどくさいですが、これも作れます。
「10、20、30」のように大雑把なナビゲーション
総ページ数がかなり多い時に、5ページ単位ではなく、大きくページ移動できる機能です。
WP-PageNaviでは「1 2 3 4 5… 10 20 30…」のような感じに表示されています。
作れそうですが条件分岐がめんどくさそうですね…。
以上を前提に、今回は次の3つを実装したいと思います。
- 前後5ページのナビゲーション
- 前後のページへのナビゲーション
- ページの省略
その他についても条件分岐を考えるだけなので、流用して実装することはできると思います。
ページネーション各機能のコード
先ほどの3つの機能のコードを考えていきます。
- 前後5ページのナビゲーション
- 前後のページへのナビゲーション
- ページの省略
最終ページの取得はAPIを叩いたasyncData内で行うものとして、以下、computedに実装していきます。
前後5ページのナビゲーション
条件分岐がけっこう難しい気がします。
評価が甘いかもしれませんが、上から順にelse ifで読んでください。
- 最終ページが5以下なら1~最終ページを表示
- 1~3ページでは1開始で、終了は5まで
- 最終-2~最終ページでは最終-4開始で、最終ページまで
- それ以外では、現在のページ-2開始で、現在のページ+2まで
最大表示数を5としていますが、ここは人によると思うので、「this.num_pages」に数を定義しておき、
export default {
data () {
return {
num_pages: 5,
}
},
}
偶数の場合には先の方が多く表示されるようにします。
(6個表示の場合は「①② 3 ④⑤⑥」の形)
<script>部分のコード↓。
export default {
computed: {
//数字で表示するリンクの配列を返す
pages() {
//前後の表示数の指標となる値を先に計算
const value = (this.num_pages-1)/2
//最初/最後の初期値として「それ以外」の場合の値を先に計算
let start = this.current - Math.floor(value)
let last = this.current + Math.ceil(value)
if ( this.total <= this.num_pages ) {
//最終ページが5以下なら1~最終ページ
start = 1
last = this.total
} else if ( this.current <= 1 + Math.floor(value) ) {
//1~3ページでは1開始で、終了は5まで
start = 1
last = this.num_pages
} else if ( this.current >= Math.ceil(value) ) {
//最終-2~最終ページでは最終-4開始で、最終ページまで
start = this.total - this.num_pages + 1
last = this.total
}
//戻り値用の配列
let pages = []
//開始番号から終了番号までの整数を追加
for ( let i = start; i <= last; i++ ) {
pages.push(i)
}
//開始番号と終了番号を変数としてキープ(後で使う)
this.start_num = start
this.last_num = last
return pages
},
},
}
それぞれの条件で開始番号と終了番号を決定し、その区間を配列にして返します。
「num_pages」が偶数の場合の処理を、切り捨て(Math.floor)と切り上げ(Math.ceil)で処理しています。
「this.start_num」と「this.last_num」は今は使いませんが後で使うので代入しておきます。
<template>部分のコードはこんな↓感じ。
<ul>
<li v-for="i in pages">
<nuxt-link v-if="i != current" :to="'?page=' + i">{{i}}</nuxt-link>
<span v-else>{{i}}</span>
</li>
</ul>
先ほど計算させた「pages」でループを回し、現在のページの場合には<span>、そうでない場合には<nuxt-link>で出力します。
前後のページへのナビゲーション
簡単です。 必要な条件は次のような感じでしょうか。
- 「前のページへ」は「現在のページが2以上」で表示
- 「次のページへ」は「現在のページが最終ページ未満」で表示
条件を満たす時には誘導先のページ番号を、そうでない場合は0を返すようにします。
computed: {
//前のページのページ番号
prevPage() {
if ( this.current > 1 ) {
return this.current-1
}
return 0
},
//次のページのページ番号
nextPage() {
if ( this.current < this.last ) {
return this.current+1
}
return 0
},
},
「this.current」「this.last」はasyncData内で定義されている「現在のページ番号」と「最後のページ番号」です。
出力部分のコードはこんな↓感じ。
//前のページへのリンク
<nuxt-link v-if="prevPage" :to="'?page='+prevPage">«</nuxt-link>
//次のページへのリンク
<nuxt-link v-if="nextPage" :to="'?page='+nextPage">»</nuxt-link>
「v-if」で戻り値を評価しているので、0の場合はリンクが表示されません。
内側の記号は、他の記号でも文字でも、画像でも変更可能です。
ページの省略
数字のリンクを作った後なら簡単です。
- 数字リンクの開始番号が2以上なら手前の省略を表示
- 数字リンクの終了番号が最終番号未満なら省略を表示
コードはこんな↓感じ。
export default {
computed: {
prevDot() {
return this.start_num > 1
},
nextDot() {
return this.last_num < this.last
},
},
}
結果を真偽値(boolean)で返し、真なら「…」を表示、偽なら非表示にします。
最初、「this.start_num」と「this.last_num」はcomputedやmethodsで計算させてみたんですが、処理順の問題なのかうまく欲しい値が返らなかったのでdataの方で定義することにしました。
<template>部分のコードはこんな↓感じ。
<ul>
<li v-if="prevDot">…</li>
<li v-for="i in pages">
<nuxt-link v-if="i != current" :to="'?page=' + i">{{i}}</nuxt-link>
<span v-else class="current">{{i}}</span>
</li>
<li v-if="nextDot">…</li>
</ul>
ここまでのコードをまとめてコンポーネント化
せっかくなのでコンポーネントにして独立させたいと思います。
親ページから「current」(=現在のページ番号)と「last」(=最終ページ番号)をpropsで受け取ります。
最終ページ番号は序盤に紹介したのでそちらを参考にしてください。 後日、カテゴリページの作り方で実践のコードは紹介します。
<script>部分のコード
export default {
props: [
'current',
'last',
],
data () {
return {
num_pages: 5,
start_num: 1,
last_num: 5,
}
},
computed: {
pages() {
const value = (this.num_pages-1)/2
let start = this.current - Math.floor(value)
let last = this.current + Math.ceil(value)
if ( this.last <= this.num_pages ) {
start = 1
last = this.last
} else if ( this.current <= 1 + Math.floor(value) ) {
start = 1
last = this.num_pages
} else if ( this.current >= Math.ceil(value) ) {
start = this.last - this.num_pages + 1
last = this.last
}
let pages = []
for ( let i = start; i <= last; i++ ) {
pages.push(i)
}
this.start_num = start
this.last_num = last
return pages
},
prevPage() {
if ( this.current > 1 ) {
return this.current-1
}
return 0
},
nextPage() {
if ( this.current < this.last ) {
return this.current+1
}
return 0
},
prevDot() {
return this.start_num > 1
},
nextDot() {
return this.last_num < this.last
},
},
}
<template>部分のコード
<div>
<nuxt-link v-if="prevPage" :to="'?page='+prevPage">«</nuxt-link>
<ul>
<li v-if="prevDot">…</li>
<li v-for="i in pages">
<nuxt-link v-if="i != current" :to="'?page=' + i">{{i}}</nuxt-link>
<span v-else class="current">{{i}}</span>
</li>
<li v-if="nextDot">…</li>
</ul>
<nuxt-link v-if="nextPage" :to="'?page='+nextPage">»</nuxt-link>
</div>
必要最小限のコードにしているので、CSSでの装飾などカスタマイズする場合には必要なタグを追加してください。
以上、WordPressからお届けしました!