Google Bloggerのカスタマイズ備忘録ー10:忘れてた写真ギャラリー

0

Photo Gallery の改修 と 検索対応ページネーション
Photo Gallery の改修 と 検索対応ページネーション
 Photo of the Life のブログテーマ修正は一段落したですが、写真ギャラリーの改修を施しました。 写真ギャラリーの改修内容は...
  • サイドバーなのど余計なパーツ削除
  • 画面スワイプで前後記事へのジャンプ機能
  • 検索一覧対応のページネーションへ差し換え
でしたが、Bloggerの仕様で検索結果一覧ではページ番号付きのページネーションが動作しない件も、検索結果一覧に対応したページネーションに差し換える事にしました。

Photo Gallery の改修

 『誰も見ないだろうから...』とほったらかしだった Photo Gallery を、見易い様に改修しました。 単なる写真ギャラリーなので、サイドバーなどの余計なものは削除して写真が並んでいるだけのシンプルな見た目にしてあります また、スマホで覗いてみるとエラク使い難いので、小さな画面にも対応する様に「レスポンシブ風」にしてみました。 ついでに、Photo of the Life からのリンク画像も作り直しました。(写真はあまり追加してませんが、そのうち追加するかも...)
Photo Gallery へのリンク画像
Photo Gallery へのリンク画像

スマホでの表示例
スマホでの表示例

スマホでのスワイプ操作に対応

 ちなみに、スマホでは前後記事や前後一覧への移動にスワイプ動作を使った方が便利なので、スワイプ検出機能も追加してみましたが、今の画面がスワイプ操作に同調する様な動作は出来ていません。 なお、下スワイプは「ページのリロード」に使われるので、上スワイプを「前画面に戻る」機能してみましたが、動作が不安定なのでコメントアウトしてあります。

 スワイプ対応の実装にあたって、いつも悩ましいのが、「Priviuous」と「Next」の関係で、記事一覧(トップページ)では「Next」が左側にあり「Priviuous」が右側にあるので、「Priviuous」は古い記事一覧を意味します。 ところが、投稿ページになると、「Priviuous」は新しい投稿を指す事が多く、混乱してしまいます。 なので、右を差すのは古い投稿や一覧と決めて、スワイプ動作もそれに従いました。 私の Photo Gallery ではスワイプ動作と対応するclass名は以下の様になっています。

スワイプ動作class名
左にスワイプ 古い一覧へ a.blog-pager-older-link
古い記事へ a.right-older-link
右にスワイプ 新しい一覧へ a.blog-pager-newer-link
新しい記事へ a.left-newer-link
上にスワイプ 前のページへ戻る a.center-back-link

 これは Photo of the Life への実装ではなく、Photo Gallery への実装なのですが、下記コードを</body>の上あたりに入れて、前後投稿へ遷移するボタン等のClass名を各自のClass名に書き換えれば良いでしょう。

<!-- スワイプで前後へ遷移させる -->
<script type='text/javascript'>
//<![CDATA[
(function() {
    var touchstartX = 0;
    var touchendX = 0;
    var thresholdX = 200; // スワイプと認識するX距離(ピクセル)
    var touchstartY = 0;
    var touchendY = 0;
    var thresholdY = 300; // スワイプと認識するY距離(ピクセル)

    document.addEventListener('touchstart', function(e) {
        touchstartX = e.changedTouches[0].screenX;
        touchstartY = e.changedTouches[0].screenY;
    }, false);

    document.addEventListener('touchend', function(e) {
        touchendX = e.changedTouches[0].screenX;
        touchendY = e.changedTouches[0].screenY;
        handleSwipe();
    }, false);

    function handleSwipe() {
      if (!$('html').hasClass('fancybox-active')) {
        // ■Fancybox が開いていないなら実施
        if (touchendX < touchstartX - thresholdX) {
            // 左にスワイプ(次の記事へ)
            var nextLink = document.querySelector('a.blog-pager-older-link');
            if (nextLink) {
                window.location.href = nextLink.href;
            }
            var prevLink = document.querySelector('a.right-older-link');
            if (prevLink) {
                window.location.href = prevLink.href;
            }
        } else
        if (touchendX > touchstartX + thresholdX) {
            // 右にスワイプ(前の記事へ)
            var prevLink = document.querySelector('a.blog-pager-newer-link');
            if (prevLink) {
                window.location.href = prevLink.href;
            }
            var prevLink = document.querySelector('a.left-newer-link');
            if (prevLink) {
                window.location.href = prevLink.href;
            }
/* 上スワイプは不安定なので使わない
        } else
        if (touchendY < touchstartY - thresholdY) {
            // 上にスワイプ(前の履歴へ)
            var prevLink = document.querySelector('a.center-back-link');
            if (prevLink) {
                window.location.href = prevLink.href;
            }
*/
        }
      }
    }
})();
//]]>
</script>
 スワイプの距離を短くするとフリックで反応しちゃうので、充分にスワイプさせるため大きめに設定してあります。 残念ながら、今の実装のままでは、スワイプしたのかが判らないので、UIとしてはイマイチです。 そのうち何とかしたいと思っていますが、いつになるやら...

検索対応ページ番号付きページネーション

 大昔に「Bloggerの検索ではページ番号付きページネーションが効かない」ので、検索は15件までにしていました。 ただ、投稿記事が多くなると、フリーワードで検索しても表示されない投稿記事が増えてきました。 そこで、検索結果一覧でもページ番号付きページネーションを動作させる方法を、いつもの様にGoogle先生に尋ねてみると、「IB-Noteさんのページネーションがお勧め」という事でした。
 早速導入してみると、これまで通り Nextボタン や Previousボタン や Firstボタン や Lastボタン などがありません。 見た目や機能は変えたくなかったので、IB-Noteさんのコードを小改造させて頂きました。 小改造の内容は、出力先のClass名変更と追加ボタンに対応させるコードを入れ、CSSは明暗切換え用の外部CSSファイルを修正して対応しました。 なお、ボタンを追加したので、document.querySelector()関数ではなくdocument.querySelectorAll()に変更してあります。

 IB-Noteさんが掲載されてるコードをコピーして、上記の対応を施したものが以下のコードです。 これを本テーマの古いページネーション(</body>の上あたり)と入れ換えます。

テーマに追加したコード(</body> の上)
<!-- インデックス か アーカイブ の時だけ動作させる -->
<b:if cond='data:blog.pageType in {&quot;index&quot;, &quot;archive&quot;}'>
  <div class='pagination'></div>
  <script type='text/javascript'>
    const blogUrl = "<data:blog.homepageUrl.canonical/>"
    const label = "<b:eval expr='data:blog.searchLabel'/>"
    const query = "<b:eval expr='data:blog.searchQuery'/>"
    const selector = '.blog-pagenav'; // ページネーションのセレクタ
    const maxResults = 15; // 1ページあたりの表示数
    const firstText = '«';
    const lastText = '»';
    const prevText = '‹';
    const nextText = '›';

    //<![CDATA[
    insertLink(selector, maxResults);

    function insertLink(selector, maxResults) {
      // フィードURLを作成
      let feedUrl = blogUrl;
      if (query == '') {
        // トップ+ラベルページ
        feedUrl += 'feeds/posts/summary';
        if (label != '') {
          feedUrl += '/-/' + encodeURIComponent(label);
        }
        feedUrl += '?alt=json&max-results=1';
      } else {
        // 検索ページ
        feedUrl += 'feeds/posts/summary?alt=json&max-results=99&q=' + encodeURIComponent(query);
      }

      // フィードを取得
      fetch(feedUrl)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          // 現在のページ番号を算出
          let currentUrl = location.href;
          let currentPage = 1;
          if (currentUrl.indexOf("#Page-") > -1) {
            let no = currentUrl.substring(currentUrl.indexOf("#Page-") + 6);
            currentPage = parseInt(no);
          }

          // ページ総数を算出
          let totalResults = parseInt(json.feed.openSearch$totalResults.$t, 10);
          let totalPage = Math.floor(totalResults / maxResults);
          if ((totalResults % maxResults) > 0) {
            totalPage += 1;
          }

          // ページネーションを作成
          var currentPageM1 = currentPage - 1;
          var currentPageP1 = currentPage + 1;
          let pager = '';
          // 先頭へ
          if (currentPage > 1) {
            if (totalPage > 5) {
              pager += '<a class="displaypageNum" id="Page-1" href="">' + firstText + '</a>';
            }
            pager += '<a class="displaypageNum" id="Page-' + currentPageM1 + '" href="">' + prevText + '</a>';
          }
          // 各ページ
          for (i = 1; i <= totalPage; i++) {
            if (i == currentPage) {
              // 現在の投稿
              pager += '<span class="pagecurrent">' + i + '</span>';
            } else if ((Math.abs(i - currentPage) < 2) || (i == 1) || (i == totalPage)) {
              // currentから2個未満のものは表示
              pager += '<a class="displaypageNum" id="Page-' + i + '" href="" onclick="redirectpage(' + i + ');return false">' + i + '</a>';
            } else if (Math.abs(i - currentPage) == 2) {
              pager += '<span class="displaypageNum-ellipsis">…</span>';
            }
          }
          // 最後へ
          if (currentPage < totalPage) {
            pager += '<a class="displaypageNum" id="Page-' + currentPageP1 + '" href="">' + nextText + '</a>';
            if (totalPage > 5) {
              pager += '<a class="displaypageNum" id="Page-' + totalPage + '" href="">' + lastText + '</a>';
            }
          }

          // HTMLへ書き出す
          const pagerElem = document.querySelector(selector);
          if (pagerElem != null) {
            pagerElem.innerHTML = pager;
          }

          // ページネーションにURLを設定
          for (i = 1; i <= totalPage; i++) {
            if (i == currentPage) continue;
            if (query == '') {
              // トップ+ラベルページ
              setUrl1(i, totalResults);
            } else {
              // 検索ページ
              setUrl2(i, totalResults);
            }
          }
        })
        .catch((error) => {
          console.log(error);
        });
    }

    // 指定したページのURLを設定(トップ+ラベルページ)
    function setUrl1(targetPage, totalResults) {
      // 指定したページのインデックスを算出
      let startIndex = (targetPage - 1) * maxResults; // これでイイの?
      if (startIndex <= 0) startIndex = 1;

      // フィードURLを作成
      let feedUrl = blogUrl + 'feeds/posts/summary';
      if (label != '') {
        feedUrl += '/-/' + encodeURIComponent(label);
      }
      feedUrl += '?alt=json&orderby=published&start-index=' + startIndex + '&max-results=1';

      // フィードを取得
      fetch(feedUrl)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          // 一番新しい記事の日付を取得
          const date = encodeDate(json.feed.entry[0].published.$t);

          // ページURLを作成
          let href = blogUrl + 'search';
          if (label != '') {
            // ラベルページの場合はlabelをつける
            href += '/label/' + encodeURIComponent(label);
          }
          href += '?max-results=' + maxResults;
          if (targetPage != 1) {
            href += '&updated-max=' + date;
          }
          href += '#Page-' + targetPage;

          // hrefにURLを設定
          const liSelector = selector + ' #Page-' + targetPage;
          const liElem = document.querySelectorAll(liSelector);	// 複数検索
          if (liElem != null) {
            liElem.forEach((element) => {
              element.href = href; // 全テキストを書き換え
            });
          }
        })
        .catch((error) => {
          console.log(error)
        });
    }

    // 指定したページのURLを設定(検索ページ)
    function setUrl2(targetPage, totalResults) {
      let startIndex = (targetPage - 1) * maxResults;
      let href = blogUrl + 'search?max-results=' + maxResults + '&start=' + startIndex + '&q=' + encodeURIComponent(query) + '#Page-' + targetPage;
      const liSelector = selector + ' #Page-' + targetPage;
      const liElem = document.querySelectorAll(liSelector);		// 複数検索
      if (liElem != null) {
        liElem.forEach((element) => {
          element.href = href; // 前テキストを書き換え
        });
      }
    }

    // 日付をRFC3339タイムスタンプ形式で取得
    function encodeDate(dateStr) {
      dateStr = dateStr.substring(0, 19) + dateStr.substring(23, 29);
      return encodeURIComponent(dateStr);
    }
    //]]>
  </script>
  <style>
    .displaypageNum:hover {
      background:#359BED;
      text-decoration:none;
      color: #fff;
      transition: 0.3s;}
    }
  </style>
</b:if>

 コードの下に付いている<style>部分は元スクリプト内容の殆どを削除し、最低限の容姿を表すだけで、外部CSS(明暗切換え用)の古いページネーション用のものを修正して使っています。 このため、見た目は古いページネーションと変わりありません。

 これで、ブログ内検索の際に多数の記事がヒットしても、ページネーションが有効に働いてヒット全件を一覧表示させられる様になりました。 IB-Note の Fuma 様、ありがとうございました。

検索結果画面で記事を開いた場合の対応

 ただ、困ったことに(Label検索も以前から同じでしたが...)、一覧表示された記事を開いた場合に、前の記事と後の記事が検索結果一覧の記事ではなく、全一覧記事上の前後記事になってしまう事です。 地味になんとかしたい問題だけど、Google先生は『検索結果の記事をクリックして表示した場合は、前後ページネーションを消すのが良い』という回答でした。
 仕方ないので、Google先生の回答に従って検索結果一覧から飛んできた記事ページでは、前後記事へのページネーションボタンは非表示にして、「元のページへ戻る」ボタンを追加表示することにしました。 元のページへ戻れば、大抵は検索結果一覧画面なので対策としては悪くないと思います。

検索系一覧画面からのリンク対応
検索系一覧画面からのリンクジャンプ対応

 既に前後ページネーションのボタンに記事タイトルを付加する機能を盛り込んであったので、そのコードに検索系から飛んできたのかをチェックし、検索系から来たのなら前後ページボタンを非表示にしつつ、元のページへ戻るボタンを表示に設定し、検索系から来たのでなければ前後ページボタンに記事タイトルを付加する処理にしました。 下記のコードをカスタマイズ備忘録ー9で仕込んだ 前後記事へのページネーションを修正 部分とそっくり入れ換えちゃえばOKです。

テーマで入れ換えたコード(</body> の上)
<!-- DOMのツリー構造が完了したら表示に設定する -->
<!-- 検索系ページから飛んできたなら前後ページネーションは非表示 -->
<!-- 検索系じゃないなら前後記事のページネーション用タイトル抽出 -->
<!-- 投稿記事ページの時だけ動作させる -->
<b:if cond='data:view.isPost'>
<script>
//<![CDATA[
window.addEventListener('DOMContentLoaded', searchJumpChecker);
function searchJumpChecker(){
  var ref = document.referrer; // 前のページのURLを取得
  if (ref && (ref.includes("/search?q=") || ref.includes("?&max-"))) {
    // 検索系ページから飛んできたなら
    // ページネーションは非表示
    var pPosts = document.getElementById('blog-pager-newer-link');
    if (pPosts) {
        pPosts.style.display = 'none'; // 非表示にする
    }
    var pPosts = document.getElementById('blog-pager-older-link');
    if (pPosts) {
        pPosts.style.display = 'none'; // 非表示にする
    }
    // 戻るボタンを表示
    var pPosts = document.getElementById('blog-pager-back-link');
    if (pPosts) {
        pPosts.style.display = 'block'; // 非表示にする
    }
  } else {
    // 非検索系ページから飛んできた
    // 前後記事のページネーション用タイトル抽出
    setPageTitle('Blog1_blog-pager-older-link');
    setPageTitle('Blog1_blog-pager-newer-link');
  }
}
// 前後記事のページネーション用タイトル抽出
function setPageTitle(objname){
  let obj=document.getElementById(objname);
  if(!obj) return;
  let link=obj.getAttribute('href');
  const page_path=link.split(location.hostname)[1];
  let feedUrl='/feeds/posts/summary?alt=json&path='+page_path+'&max-results=1';
  fetch(feedUrl)
  .then(response=>response.json())
  .then(json=>{
//	if(json.feed.entry&&json.feed.entry.length>0) obj.textContent=json.feed.entry[0].title.$t;obj.setAttribute('title',json.feed.entry[0].title.$t);
	if(json.feed.entry&&json.feed.entry.length>0) obj.textContent=json.feed.entry[0].title.$t; // title(吹き出し)は変えない
  })
  .catch(error=>console.log(error));
}
//]]>
</script>
</b:if>

あとがき

 IT技術者が作ったパーツはさすがに洗練されていますねぇ。 見習いたいところですが、年寄りにはハードルが高すぎます。💦
Sponsored Link

Sponsored Link

0 件のコメント :

コメントを投稿

Sponsored Link