【Blogger】ページ番号付きドロップダウンページャー

Blogger のデフォルトのページャーは、「前の投稿」「次の投稿」のリンクというシンプルな構成です。

これだと記事数が増えてくるとページ移動が大変なので、記事一覧に番号付きページャーを導入しようと思いました。

ページ番号が1、2、3……と横並びになっているものはいくつか見つかったのですが、私の希望するドロップダウンでページを選べるタイプのページャーはいくら探しても見つからず。自作することにしました。

この記事では、Blogger ブログにページ番号付きドロップダウンページャーを導入する方法を紹介します。

ページャーの仕様と特徴

<select><option> を使ったドロップダウンメニューと前後ページへのリンクを組み合わせたシンプルな構成のページャーです。

投稿一覧ページとラベルページに対応しており、複数ラベルを組み合わせたページ(例:/search/label/ブログ+Blogger)でも使えます。ただし、検索ページやアーカイブページには非対応です。

また、このページャーはブログのフィードを利用しているため、フィードを無効にしているブログでは使えません。

投稿が1件もない場合は、ページャー自体が非表示になります。

ドロップダウン型ページャーの導入方法

ラベルページでの表示件数を変更する

Blogger の設定で 1 ページあたりの表示件数を 10 件に指定していても、ラベルページでは最大で 20 件表示されてしまいます。

このままだとラベルページだけページャーの件数計算と実際の表示件数が食い違ってしまうため、テーマ側でラベルページも 10 件表示になるよう揃えていきます。

いくつか方法があるのですが、今回は以下の記事の方法を参考にしました。

投稿一覧に関する b:loop とその中身を、

<b:loop values='data:posts' var='post'>
  <!-- 投稿一覧に関するコード -->
</b:loop>

以下のように書き換えます。

<b:loop index='pi' values='data:posts' var='post'>
  <b:tag cond='data:view.isLabelSearch and data:pi gte 10' name='noscript'>
    <!-- 投稿一覧に関するコード -->
  </b:tag>
</b:loop>

この変更を行うと、ラベルページでは 11 件目以降の記事が <noscript> タグで囲まれるようになります。

これにより、JavaScript が有効な環境では 11 件目以降が読み込まれず、今回導入するページャーを使って 10 件ずつページ移動できるようになります。

一方、JavaScript が無効な環境では従来通り 20 件表示され、Blogger 標準のページャーがそのまま機能する仕組みです。

なお、投稿が 10 件未満しか表示されない場合は、以下の記事にも書かれているように、記事の本文内に <!--more--> を入れて表示量を調整してみてください。

デフォルトのぺージャーを非表示にする

ページャーが 2 つ表示されてしまうのを防ぐため、元々あったページャーのほうを非表示にします。

テーマによってまちまちなので一概には言えないのですが、例えば QooQ の場合こちらのコードを、

<b:include name='nextprev'/>

以下のように書き換えます。

<b:if cond='(data:view.isMultipleItems and data:posts.length != 0) or data:view.isPost'>
  <b:tag cond='data:view.isMultipleItems and !data:view.search.query and !data:view.isArchive' name='noscript'>
    <b:include name='nextprev'/>
  </b:tag>
</b:if>

記事が 1 件以上ある検索・アーカイブページでは元々のページャーを表示しつつ、トップ・ラベルページではその部分を <noscript> タグで囲む記述にしています。

こうしておくことで、JavaScript が有効な環境では標準のページャーが非表示になる代わりに今回導入するドロップダウンページャーが表示され、逆に JavaScript が無効な環境では標準のページャーがそのまま表示されるため、どちらの環境でもページ遷移を行えます。

ページャーのスクリプトを設置する

このページャー作成にあたって以下のページを参考にしました。

以下のコードを、元々の投稿一覧ページャーの近くに追加します。

<b:if cond='data:view.isMultipleItems and !data:view.isArchive and !data:view.search.query'>
  <nav aria-label='ページャー' class='pagination'/>
  
<script>
// Blogger のデータタグから基本URLと検索ラベル名を取得
let feedUrl = &#39;<b:eval expr='data:blog.homepageUrl.canonical path (&quot;feeds/posts/summary&quot; + (data:view.isLabelSearch ? &quot;/-/&quot; : &quot;&quot;))'/>&#39;;
let topUrl = &#39;<b:eval expr='data:blog.homepageUrl.canonical path (data:view.isLabelSearch ? &quot;/search/label/&quot; : &quot;&quot;)'/>&#39;;
const labelName = &#39;<b:eval expr='data:view.search.label'/>&#39;;

//<![CDATA[
const pager = document.querySelector('.pagination');
const maxResults = 10; // 1 ページあたりの表示件数
let currentPage = selectedPage = 1;

// ラベル検索時は URL エンコードしてフィードとトップ URL を調整
if(labelName){
  feedUrl += labelName.split('+').map(encodeURIComponent).join('/');
  topUrl += labelName.split('+').map(encodeURIComponent).join('+');
}

feedUrl += '?alt=json';

displayPager()

// ページネーション(UI)の動的生成
async function displayPager(){
  try{
    // 総記事数を取得するためフィードをリクエスト
    const response = await fetch(`${feedUrl}&max-results=0`);
    const json = await response.json();
    const totalResults = parseInt(json.feed.openSearch$totalResults.$t);

    if(totalResults === 0) return;

    // 総ページ数の計算
    const totalPages = Math.ceil(totalResults / maxResults);

    // ハッシュから現在のページ番号を取得(例: #page2)
    if(/#page\d+/.test(location.hash)) currentPage = parseInt(location.hash.slice(5));

    let html = '';
  
    // 「newer」ボタンの描画処理(1 ページ目へはトップ URL、それ以外は jumpPage 関数を呼ぶ)
    html += (totalPages >= 2 && currentPage >= 2) ? (currentPage === 2 ? `<a aria-label='新しい投稿' class='page-link' href='${topUrl}'>newer</a>` :  `<a aria-label='新しい投稿' class='page-link' href='#' onclick='jumpPage(${currentPage - 1});return false'>newer</a>`) : `<span class='page-link'></span>`;
    
    // ページ選択セレクトボックスの生成
    html += `<select aria-label='ページを選択' class='page-select' name='dropdown-pager' onchange='if(this.value!="")jumpPage(this.value)'>`;

    for(let i = 1; i <= totalPages; i++){
      html += `<option value='${i}'${i === currentPage ? ' selected' : ''}>page: ${i} / ${totalPages}</option>`;
    }

    html += `</select>`;
    
    // 「older」ボタンの描画処理
    html += (totalPages >= 2 && currentPage !== totalPages) ? `<a aria-label='古い投稿' class='page-link' href='#' onclick='jumpPage(${currentPage + 1});return false'>older</a>` : `<span class='page-link'></span>`;

    pager.innerHTML = html;
  } catch(error){
    console.log(error.stack)
  }
}
  
// 指定したページへ遷移する処理
async function jumpPage(i){
  selectedPage = parseInt(i);

  // 1 ページ目への遷移はトップ URL へ移動
  if(selectedPage === 1){
    location.href = topUrl;
  }else{
    try{
      // 目的ページの先頭記事の投稿日時を取得
      const response = await fetch(`${feedUrl}&max-results=1&start-index=${maxResults * (selectedPage - 1)}`);
      const json = await response.json();
      const entry = json.feed.entry;

      if(entry.length === 0) return;

      // 日時指定パラメータ(updated-max)を付与して該当ページヘリダイレクト
      location.href = `${topUrl + (labelName == '' ? '/search' : '')}?updated-max=${entry[0].published.$t.slice(0,19)}%2B09:00&max-results=${maxResults}#page${selectedPage}`;
    }catch(error){
      console.log(error.message)
    }
  }
}
//]]></script>
</b:if>

コード内にコメントを入れているので詳しい解説は省きますが、簡単な流れとしては以下の通りです。

  1. フィードから総記事数を取得して総ページ数を計算
  2. ドロップダウンメニューと前後リンクを生成
  3. ページ選択時に移動先の投稿日時をフィードから取得
  4. 日時基準の URL(updated-max)を組み立てて該当ページへジャンプ

ページャーをクリックしてから URL を作成するため遷移に少しラグはありますが、まあそこまで気にならない程度かなと思います。

個人的なこだわりポイントは、なるべく取得するフィードの量が少なくなるようにしたこと、複数ラベルページにも対応したこと、1 ページ目に遷移するときはパラメータが付いていない綺麗な URL を設定するようにしたことです。

検索因子が複雑になるとフィードの取得が困難になることから検索ページへの導入は諦めてしまいましたが、それ以外は概ね自分の希望するページャーを作ることができました。

CSS の例は以下の通りです。ご自身のブログのデザインに合わせて適宜調整してください。

.pagination {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  max-width: 360px;
  margin: auto;
  padding: 32px 16px 0;
  line-height: 1;
}
.pagination > :not(span) {
  height: 48px;
  color: #333;
  background-color: #eee;
  outline: none;
  line-height: 48px;
}
.page-link {
  width: 72px;
  text-align: center;
  user-select: none;
}
.page-select {
  flex: 1;
  padding: 0 16px;
  -webkit-appearance: none;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, #333 50%), linear-gradient(135deg, #333 50%, transparent 50%);
  background-position: calc(100% - 22px) 50%, calc(100% - 16px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  cursor: pointer;
}

あとがき

Blogger ブログにページ番号付きドロップダウンページャーを導入する方法を紹介しました。横並びの番号付きページャーよりもコンパクトなので、ブログの UI をミニマムにしたい方におすすめです。

当ブログでこのページャーを導入してから 1 年以上が経ち、特に不具合のお問い合わせもないので順調に動作しているようです(お問い合わせが来るほどのアクセス数がないとも言う)。この記事自体も昨年7月に下書きを作成してからずっと放置していたもので、重い腰を上げて 1 年越しにようやく公開することができました。

この記事や過去のカスタマイズがどなたかのお役に立てたら幸いです。ここまで読んでいただきありがとうございました!

編集
ホーム