【Blogger】関連記事表示のお手軽プラグイン(改良版)

2021/03/13
27
thumbnail
画像:Roserodionova - jp.freepik.com

新たにオリジナルの関連記事表示を作成し、今まで使っていたものと入れ替えました。

以前のものでも機能的には問題ありませんでしたが、内部処理の効率が良くない部分があったので、旧バージョンをお使いの方は今回のものへの変更をおすすめします。

改良ポイント

document.write()不使用

以前の関連記事表示はdocument.write()を使っていてPSIに怒られていたので、使用しない形に変更しました。

コードの最適化

コードの構成を最適化し、高速化を図りました。

複数ラベルに対応

記事にラベルが複数ある場合、いずれかのラベルに該当する記事をランダムに表示します。

カスタマイズ性UP

コード・CSSを圧縮していない状態でご紹介するので、手軽にカスタマイズして頂けます。

導入方法

テーマのバックアップ

HTMLの編集をするので、バックアップを取っておきましょう。

管理画面から「テーマ」→「バックアップ」を選択し、ダウンロードしてファイルを保存します。

backup

HTMLの編集

「テーマ」→「HTMLを編集」をクリックし、編集画面を開きます。

今回のコードでは投稿ウィジェット内のデータを使うので、投稿ウィジェット内のお好きな位置に以下のコードをコピー&ペーストしてください。

<b:if cond='data:blog.pageType == "item"'>
  <div id='related-posts'></div>
  <script type='text/javascript'>
    const rp_current = "<data:post.url/>";
    const rp_max = 6; // 表示記事数
    const rp_head = "関連記事"; // タイトル

    //<![CDATA[
    (function(root, func) {
      if (!root.RelatedPosts) {
        root.RelatedPosts = func();
      }
    })(this, function() {
      let _this = function RelatedPosts_constructor() {};
      let count = 0;
      let limit = 0;
      let urls = [];
      let titles = [];
      let dates = [];
      let imgs = [];

      // フィード数分をカウント
      _this.counter = function RelatedPosts_counter() {
        limit++;
      };

      // 関連記事の書き込み
      function RelatedPosts_write() {
        // 重複処理
        let dups = [];
        let indexs = [];
        for (let i = 0; i < urls.length; i++) {
          if (dups.indexOf(urls[i]) == -1) {
            dups.push(urls[i]);
            indexs.push(i);
          }
        }

        // ランダムインデックス生成
        let idx, items = [];
        let a = [...Array(indexs.length).keys()];
        while (a.length > 0) {
          idx = Math.floor(Math.random() * a.length);
          items.push(a[idx]);
          a.splice(idx, 1);
        }

        // 関連一覧の作成
        let html;
        let i = 0;
        if (indexs.length > 0) {
          html = '<div class="related-text">' + rp_head + '</div><div class="p-container">';
          while (i < indexs.length && i < rp_max) {
            html += '<a href="' + urls[indexs[items[i]]] + '"><figure class="related-img"><img alt="" width="250" height="150" src="' + imgs[indexs[items[i]]] + '"/></figure><div class="related-date">' + dates[indexs[items[i]]] + '</div><div class="related-title">' + titles[indexs[items[i]]] + '</div></a>';
            i++;
          }
          html += '</div>';
          document.getElementById("related-posts").innerHTML = html;
        }
      }

      // フィードデータ追加
      _this.add = function RelatedPosts_add(json) {
        let m = rp_current.match(/^https?:\/\/(.+$)/);
        let http  = '';
        let https = '';
        if (m != null) {
          http  = 'http://' + m[1];
          https = 'https://' + m[1];
        }
        for (let i = 0; i < json.feed.entry.length; i++) {
          let entry = json.feed.entry[i];
          for (let j = 0; j < entry.link.length; j++) {
            if (entry.link[j].rel == 'alternate') {
              if (!(http == entry.link[j].href || https == entry.link[j].href)) {
                urls.push(entry.link[j].href);
                titles.push(entry.title.$t);
                let date = entry.published.$t.substr(0, 10).replace(/-/g, "/");
                dates.push(date);
                let img;
                if ("media$thumbnail" in entry) {
                  img = entry.media$thumbnail.url;
                  let re1 = /\/s72-.*\//;
                  let re2 = /=s72-.*$/;
                  if (img.match(re1)) {
                    img = img.replace(re1, '/w250-h150-n/');
                  } else if (img.match(re2)) {
                    img = img.replace(re2, '=w250-h150-n');
                  } else if (img.match(/default.jpg/)) {
                    img = img.replace('default.jpg', 'mqdefault.jpg');
                  }
                } else {
                  // NoImage画像
                  img = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJGufis0idCDqb29dBvyQCx5M5dHTE4bpc52w6WGtcdKpsQImJUUvWSFYn5k_IDsn1TjOhyfzw51TgvvabIrVAYjxasRmPjGTPdSKudZTYD6JA5DLW2rm-9fkahTfV4Cmn-GjsDaiCDnJ5/w250-h150-n-e365/noimg.jpg";
                }
                imgs.push(img);
              }
              break;
            }
          }
        }
        count++;
        if (count == limit) {
          RelatedPosts_write();
        }
      };

      return _this;
    });
    //]]>
  </script>
  <b:loop values='data:post.labels' var='label'>
    <script type='text/javascript'>RelatedPosts.counter();</script>
  </b:loop>
  <b:loop values='data:post.labels' var='label'>
    <b:if cond='data:label.name contains "#" or data:label.name contains "/"'>
      <script>
      (function() {
        let encoded = "<data:label.name/>"
        const keys = [/・/g, /&amp;amp;/g, /#/g, /\//g];
        const reps = ["%E3%83%BB", "&amp;", "%23", "%2F"];
        for (let i = 0; i &lt; keys.length; i++) {
          encoded = encoded.replace(keys[i], reps[i]);
        }      
        const src = "/feeds/posts/summary/-/" + encoded + "?alt=json&amp;max-results=10&amp;callback=RelatedPosts.add";
        const js = document.createElement("script");
        js.type = "text/javascript";
        js.defer = true;
        js.src = src;
        document.head.appendChild(js);
      }());
      </script>
    <b:else/>
      <script defer='defer' expr:src='"/feeds/posts/summary/-/" + data:label.name + "?alt=json&amp;max-results=10&amp;callback=RelatedPosts.add"' type='text/javascript'/>
    </b:if>
  </b:loop>
  <style>
    #related-posts {
      margin: 2em 0 0;
    }
    .related-text {
      font-size: 1.35em;
      font-weight: 700;
      text-align: center;
      background: #f7f7f7;
      padding: 10px 14px;
      border-bottom: 2px solid #ddd;
      margin-bottom: 1em;
    }
    .p-container {
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap;
    }
    .p-container:after {
      display: block;
      content: "";
      width: 32%;
    }
    #related-posts a {
      display: block;
      width: 32%;
      min-height: 200px;
      color: #444;
      box-sizing: border-box;
      margin-bottom: 1.5em;
      transition: all .3s ease;
    }
    .related-img {
      display: block;
      height: 140px;
      margin: 0;
      overflow: hidden;
    }
    .related-img img {
      display: block;
      width: 100%;
      height: 100%;
      object-fit: cover;
      border-radius: 0;
      transition: .3s
    }
    .related-date {
      font-size: .8em;
      padding: .7em 0 .2em;
      opacity: .8;
    }
    .related-title {
      font-size: .9em;
    }
    #related-posts a:hover {
      text-decoration: none;
    }
    #related-posts a:hover .related-img img {
      transform: scale(1.1);
    }
    @media screen and (max-width:519px) {
      #related-posts a {
        width: 49%;
      }
    }
  </style>
</b:if>

導入例

テーマ「Contempo」への導入例をご紹介します。

Contempoの場合、post-outer-containerというクラス名のarticleタグを探し、画像のように</article>前に設置するとちょうど良い位置(コメント欄のすぐ下)に表示されます。設置の際の参考にしてみてください。

contempo

導入した場合のイメージ画像がこちら↓

sample

スクリプト中のrp_maxで表示する最大記事数を、rp_headで「関連記事」部分の表記を設定できます。お好きな設定に変えて使ってください。

CSSについて、今回作成したコードは以前の記事の関連記事表示と互換性をもたせているため、こちらにある各スタイルにそのまま変更して頂くことが可能です。ただし、環境や設定によっては表示崩れが起きる場合があるためご注意ください。

なお、上記スクリプトの作成にあたり「バグ取りの日々」さんの記事を参考にさせて頂きました。

スクリプトに関する補足

今回のスクリプトは、現在の投稿と同じラベル(カテゴリー)のものを最新10件読み込み、それらの中からランダムに記事を表示する仕組みになっています。

ブログのフィード情報から関連記事のリンク、タイトル、投稿日、サムネ画像を取得し、整形して一覧表示します。

フィードを利用していますが、データタイプがsummaryかつmax-results=10と制限しているため、転送量が最小限で済んでいて軽量です。スクリプトはシンプルな構成ながらも、確実に重複なく&ランダムに表示されるように設計しました。

追記(2022/3/27)

初期のスクリプトはラベルに空白や特殊記号が含まれている場合、エラーが出て関連記事が表示されないという致命的な欠陥がありました。

読者の方からご報告をいただいて修正を重ねた結果、ラベルに空白や「#」「☆」のような特殊記号が含まれている場合でも正常に動作するようになりました。ご報告いただいた皆さん、ありがとうございます。

あとがき

関連記事表示を改良したところ、読み込み速度が以前と比べて明らかに速くなりました。以前は読み込み時に関連記事の表示が遅れ、サイドバーがガクっと表示される感じだったのですが見事に解消。

主な犯人はdocument.write()だったと思いますが、結構露骨に影響が出るものなんですね。うっかり使ってしまわないように気をつけねば…

27件のコメント
こんばんは^^

僕も気になったので先程フィードに関して調べていて同じ結論に至りました。説明ではカテゴリーでも「|」で論理和が可能と書かれていたのですが、何故かできませんね。適当な記号を入れたりして色々試したりしてもダメだったので検索してみると、クエリなら可能だと紹介されているQ&Aが。結果的に可能なのだから良いものの少しスッキリしない思いが残りました。

しかしFumaさんは仕事が早い^^;いつも凄いなぁ~と感心しきりです。記事の修正お疲れ様でしたm(_ _)m
ひき太郎さん、こんにちは。コメントありがとうございます。

ラベルの件ですが、確かにすっきりしない感じですよね。AND検索できるのだからOR検索も…と思いきやできないという。もし通常のラベルでOR検索できればもっとスマートに書けたと思うのですが(^^;

嬉しいお言葉、恐縮です。記事のほうもいつもチェックして頂きありがとうございますm(_ _)m
第メインブログではなく、他のブログに関連記事ウィジェットをインストールしようとします。すべての記事にラベルが一つもないのに、ウィジェットが正常に動作しますか?
>BINUBALLさん
デフォルトでは、ラベルがない投稿は関連記事が表示されない仕様になっています。if(flag == true) { ... }の部分のif文を削除すれば関連記事は表示されますが、すべての投稿からランダムに表示されることになります。

今回作成したウィジェットはあくまでもラベルを基に関連記事を表示しているので、すべての記事にラベルがない場合、動作はしますが「関連記事」ではなくなってしまいます。
if(flag==true)を削除しました。
私はいくつかテストした結果、明らかに現在の記事は関連記事Arrayから削除するコードがありますが、関連記事に現在の記事が続いてきます。 console.logで確認してみるとpost.canonicalUrlは存在しないタグとね。 currentposturlをdata:post.urlに変えたら正常に出力された。同時に、私は携帯電話をサポートまでしなければならいたので、コードが以下のように変更されました。
if (rpurl[k] == currentposturl || `${rpurl[k]}?m=1` == currentposturl) {
rpurl.splice(k, 1);
rptitle.splice(k, 1);
rpthumb.splice(k, 1);
break;
}
>BINUBALLさん
有益な情報ありがとうございます。現在の投稿URLをpost.canonicalUrlで指定していたのが良くなかったみたいですね。ご指摘の部分を検証した後、記事を修正しておこうと思います。
こんにちは。関連記事スクリプトをよく書いているが、エラーが出て関連記事が表示されないエラーが発生します。カテゴリーに空白(space)が入ると、関連記事を出力してないん。
ex) Category : Hill Climb Racing 2
https://binuball.blogspot.com/2021/05/the-last-neckbender-26k.html
>BINUBALLさん
ご報告ありがとうございます。確かにカテゴリーに空白がある場合、エラーが出ていて関連記事が表示されていないことが確認できました。

原因ですが、どうやらq型クエリの仕様が関係しているみたいです。同じカテゴリーのものでも、通常のラベル指定ではフィードが問題なく表示されていました。

ex) https://binuball.blogspot.com/feeds/posts/summary/-/Hill%20Climb%20Racing%202?alt=json-in-script&max-results=10

ただし通常のラベル指定だとOR検索ができないので、直すのは今のところ難しそうです…。ご不便をおかけしてしまいすみませんm(_ _)m

この件に関してBINUBALLさんのほうでも何か情報をお持ちでしたら、教えて頂けると幸いです。
こんにちは。
関連記事表示、こちらのコード導入させていただきました!
QooQ利用中ですが、ページ表示の際にサイドバーが遅れて表示されるのが気になっていて、 記事下部の関連記事の読み込みに時間がかかってるからかも?? と思い、こちらのコードに入れ替えたところサイドバーの表示が早くなりました(きちんと計測したわけじゃないですが、体感的に早くなった気がします)。
QooQの関連記事の仕組みと似ていそうですが、ちょっと違うんですかね。ありがとうございます ^ ^
>りもすきさん
コメントありがとうございます。お役に立てたようで嬉しいです(^^)
今後、使用されていて不具合等あれば遠慮なくお尋ねください。
こんにちは。
こちらのコードは独自ドメインに対応させることは可能でしょうか?
私のブログはblogspot.comから独自ドメインに変更したのですが、こちらのコードが動かないようなので。
>かえるさん
はじめまして。コメントありがとうございます。
コードに関してですが、Bloggerであれば独自ドメインでも問題なく使用できると思います。コードが動かない件について詳しく確認したいので、差し支えなければブログのURLを教えて頂けないでしょうか?
「コメント欄で書くのはちょっと…」という場合は、お問い合わせから送って頂ければ以後メールにて個別対応いたします。
Fumaさん、ご対応ありがとうございます。
やりとりが複数回発生することを考えると、メールでのやりとりのほうが良い気がします。
現在のテンプレートにコードを挿入した時点でご連絡させていただきます。
Contempo などの最新テーマではdata:label.isLastが作動しないです。代わりにdata:label.name == data:post.labels.last.nameを使わなければなりません。
ピドのラベルOR 検索は次のようにすれば良いようです。上の物は他のラベルが含まれています。下のようにすれば良いです。

https://itblogger-note.blogspot.com/feeds/posts/default?q=label:ブログ運営|Blogger&alt=json&max-results=10&callback=loadrp

https://itblogger-note.blogspot.com/feeds/posts/default?q=label:ブログ運営|label:Blogger&alt=json&max-results=10&callback=loadrp
>BINUBALLさん
最新テーマとの互換性に関するご指摘ありがとうございます。
問題の箇所を修正しました。
こんばんは。
当ブログの関連記事をこちらのウィジェットに変えさせて頂きました。
QooQデフォルトのウィジェットと比較して、地味な不具合とかもなく、カスタマイズ性も高いし、何かとメリットが多そうなので。
機能的には全く問題ないですが、img タグに height と width を入れておけば完璧かも?
>ふじやんさん
導入のご報告ありがとうございます。
使って頂けて嬉しいです(^^)

imgタグの件、確かにそうですね…!コードを修正しておきます。
ご指摘ありがとうございます。
最新コメントの記事トップへのリンク化でお世話になったものです。大変ありがとうございました。今回BloggerのEmporioテーマでFUMA様の関連記事プラグインを使用しようとしているのですが、記事のコメント欄の下に配置したいと考えています。そこでHTMLエディターからBlog1の<div class='footer'>の下にコピペしてみたのですが、コメントがついていない記事では表示されるようになんとかこぎつけましたが、コメントがついた記事では関連記事のボックスそのものが表示されず困っています。大変お手数ですが、どうしたらよいか教えていただけないでしょうか。あるいは配置するのに適格な場所があれば教えていただけないでしょうか。よろしくお願いします。
>匿名さん
Emporioテーマの仕様を確認したところ、コメントがついている場合は <div class='footer'> 部分が表示されないため、関連記事も表示されなくなってしまうようです。

代わりに、<b:includable id='post' var='post'>(2つある場合は2個目)の中の <div class='slide'> の </div> 直前に関連記事コードを設置してみてください。コードを特にいじっていない場合、だいたい4000行くらいの位置にあるはずです。

参考画像1

上記の通り設置した場合、以下の画像のようにコメント欄の上に関連記事が表示されます。

参考画像2
はい、自分もsludeの閉じる直前に配置してコメントの上に表示することはできました。ですが、コメントの下に配置したいのですが、それがなかなかうまくいきません。他に方法はないでしょうか。よろしくお願いします。
>匿名さん
コメント欄の下に配置となると、Emporioの仕様上簡単に設置することは難しそうです。
Contempoなど設置しやすいテーマに変えて頂くか、関連記事が設置できるようご自身でHTMLを書き換えて頂く必要があります。もっと簡単にできる方法があれば良いのですが…
お力になれず申し訳ないです。
わかりました。ありがとうございました。
はじめまして。
Emporioでこちらのコードを使わせて頂こうとしたのですが、Fumaさんがコメントで提示されていた場所にペーストしても、関連記事が表示されません。

コメント欄の上、またはサイドバーでいいので、ペーストの適切な位置を教えていただけないでしょうか?
>匿名さん
はじめまして。ご質問の件ですが、以下の画像のように<div class='slide'></div>の直後にペーストしてみてはいかがでしょうか?

参考画像

私の環境ではコメント欄の上に問題なく表示されました。こちらでもダメだった場合、申し訳ありませんが他に良い位置が思いつきません。

補足:投稿ウィジェット内でなければ投稿のURLを取得できず、関連記事が表示できないため、サイドバーに設置するのは難しいです。
Emporioで利用させて頂いてます。「ラベル」「#ラベル」のような場合に関連記事が表示されなくなってしまいます。「ラベル」単独では表示されます。便宜上、通常のラベルをカテゴリのように扱って、#付きをタグのような扱いにしています。
>匿名さん
関連記事ウィジェットのご利用ありがとうございます。
私のテストブログ(Emporio)では「ラベル」「#ラベル」のような場合も関連記事が表示されることを確認しています。
ブログのURLを教えていただければ、直接エラー等確認して問題が解決できるかもしれません。
Fumaさん、ご返信ありがとう御座います。
「ラベル」「#ラベル」の件ですが、
<div class='post-footer'></div>直後に設置したところ正常に表示されました。
わざわざご検証頂きましてありがとう御座いました。