Blogger用のランダムポストウィジェットを作成しました

更新  
公開
当サイトはアフィリエイト広告を使用しています

Blogger



Blogger用のランダムポストウィジェットを作成しました。

フィードを使ってすべての投稿からランダムにデータを取得しています。

投稿をまとめて表示するタイプと、ひとつずつ表示するタイプを作成しました。

HTML/JavaScriptガジェット(ウィジェット)での使用を想定しています。

目次

注意事項

すべての投稿からランダムにデータを取得しています。投稿数が少ないと投稿が重複して表示されるケースが増えます。

データを取得して表示するまでに時間がかかります。

「表示する投稿数」が多い場合、表示までかなり時間がかかります。

自分のテスト環境ではデータの取得に失敗するケースがやや多いように感じました。※

fetchを使ってデータを取得しています。もしかしたらcallback関数を使うほうがいいのかも?

プログラム

出力するHTMLは以下の通りです。「人気の投稿」ガジェットの構成に習いました。

<ul class="all-posts__post-list">
  <li class="all-posts__post-list-item">
    <figure class="all-posts__post-list-item-figure">
    <a class="all-posts__post-list-item-anchor" href="...">
    <img class="all-posts__post-list-item-img" width="..." height="..." alt="..." src="...">
    <figcaption class="all-posts__post-list-item-figcaption">タイトル</figcaption>
    </a></figure>
  </li>
</ul>

プログラムはHTML JavaScript CSSをまとめたものになります。

追記(2023年3月16日)CSSを一部変更。object-fit: coverを追加。

.all-posts__post-list-item-img {
  grid-area: thumnail;
  width: 72px;
  height: 72px;
  object-fit: cover;
}

まとめて表示するタイプ

まとめて表示するタイプ:クリックすると開きます
<div id="allPostsLoader" class="all-posts__loader"></div>
<div id="allPosts" class="all-posts"></div>

<script>
/*! Copyright:2023 sutajp | Released under the MIT license | https://sutasutashiki.blogspot.com/p/mit-license.html */
//<![CDATA[
/** フィードですべての投稿をランダムに取得, HTMLで出力する */
"use strict";
/**
 * HTMLを生成して出力する関数
 * @param {Array<object>} objects - [{title:string, url:string}]
 */
function createHtml(objects) {
  /** サムネイル設定 @type { {[key: string]: number | string} } */
  const thumbnailSetting = {
    /** サムネイルの幅 @type {number} */
    thumbnailWidth: 72,
    /** サムネイルの高さ @type {number} */
    thumbnailHeight: 72,
    /** サムネイルの代替テキスト @type {string} */
    thumbnailAlt: "ランダムピックアップサムネイル",
  };

  /** ul要素 */
  const ulElm = document.createElement("ul");
  ulElm.className = "all-posts__post-list";

  for (const outputObject of objects) {
    let title = outputObject.title;
    /** タグなどをエスケープしてサニタイジング(無害化)する */
    if (/[<>"'`&]/g.test(outputObject.title)) {
      title = outputObject.title.replace(/[<>"'`&]/g, function (match) {
        return {
          "<": "&lt;",
          ">": "&gt;",
          '"': "&quot;",
          "'": "&#x27;",
          "`": "&#x60;",
          "&": "&amp;",
        }[match];
      });
    }
    /**
     * テンプレートリテラルで記述
     * {@link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals MDN}
     */
    const randomPostBody = `<li class="all-posts__post-list-item">
    <figure class="all-posts__post-list-item-figure">
    <a class="all-posts__post-list-item-anchor" href="${outputObject.url}">
    <img class="all-posts__post-list-item-img" width="${thumbnailSetting.thumbnailWidth}" height="${thumbnailSetting.thumbnailHeight}" alt="${thumbnailSetting.thumbnailAlt}" src="${outputObject.thumbnail}"/>
    <figcaption class="all-posts__post-list-item-figcaption">${title}</figcaption>
    </a></figure></li>`;

    ulElm.insertAdjacentHTML("afterbegin", randomPostBody);
  }
  /** HTMLを出力 */
  const allPosts = document.getElementById("allPosts");
  allPosts.append(ulElm);
  /** HTMLを出力後, ローディング画面にdisplay:noneを設定*/
  const allPostsLoader = document.getElementById("allPostsLoader");
  allPostsLoader.style.display = "none";
}
/**
 * フィードの取得に失敗したときに実行する関数
 */
function errorHanding() {
  const allPosts = document.getElementById("allPosts");
  allPosts.insertAdjacentHTML(
    "beforeend",
    "<span>データの取得に失敗しました&#x1f641;</span>"
  );
  allPosts.style.minHeight = "50px";
  /** ローディング画面にdisplay:noneを設定 */
  const allPostsLoader = document.getElementById("allPostsLoader");
  allPostsLoader.style.display = "none";
}
//]]>
/**
 * JSONデータを取得してオブジェクトを生成する関数
 */
async function createRandomObjects() {
  /** 現在開いているページ @type {number} */
  const currentPostId = "<data:view.postId/>";
  //<![CDATA[
  /** 表示数 @type {number} */
  const numberOfDisplay = 5;

  /** サムネイル設定 @type { {[key: string]: number | string} } */
  const thumbnailSetting = {
    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter1: "w72-h72-p-k-no-nu",

    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter2: "/w72-h72-p-k-no-nu/",

    /** Youtubeのサムネイル用 */
    parameter3: "mqdefault.jpg",

    /** 記事に画像がない場合の代替画像
     * @type {string}
     * {@link https://icooon-mono.com/11396-ウィンドウアイコン/ SVGを使用}
     * {@link https://icooon-mono.com/license/ ライセンス}
     * {@link https://heyallan.github.io/svg-to-data-uri/ Data URIにエンコード}
     */
    noImage:
      "data:image/svg+xml,%3Csvg id='_x32_' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='width: 128px; height: 128px; opacity: 1;' xml:space='preserve'%3E%3Cg%3E%3Cpath class='st0' d='M464,0H48C21.492,0,0,21.492,0,48v416c0,26.508,21.492,48,48,48h416c26.508,0,48-21.492,48-48V48 C512,21.492,490.508,0,464,0z M444.664,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S434.172,35,444.664,35z M374.164,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S363.672,35,374.164,35z M303.664,35 c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S293.172,35,303.664,35z M472,464c0,4.406-3.586,8-8,8H48 c-4.414,0-8-3.594-8-8V104h432V464z' fill='%234B4B4B'%3E%3C/path%3E%3Crect x='112' y='192' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='272' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='352' class='st0' width='152' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E",
  };

  /**
   * 取得したデータを格納
   * @type {Array<object>} - [{title:string, url:string}]
   */
  let objects = [];

  /**
   * 投稿数
   * @type {Number} - json.feed.openSearch$totalResults.$t
   */
  let totalResults;

  /** 投稿数の取得の可否条件 @type {Boolean} */
  let isFirstFeed = true;
  /** 投稿データの取得の可否条件 @type {Boolean} */
  let isGetFeedObject = false;
  /** 重複チェック用配列 @type {Array} */
  let duplicateCheck = [];
  /** startIndexの初期値 @type {number} */
  let startIndex = 1;
  const maxResults = 1;

  /** ランダムにstartIndexを作成する関数 */
  function createRandomStartIndex() {
    startIndex = Math.floor(Math.random() * totalResults + 1) + 1;
    duplicateCheck.push(startIndex);
  }
  do {
    /** 1回だけ重複チェック */
    if (duplicateCheck.includes(startIndex)) {
      createRandomStartIndex();
    }
    const feedUrl =
      "/feeds/posts/summary?alt=json&start-index=" +
      startIndex +
      "&max-results=" +
      maxResults;
    try {
      const response = await fetch(feedUrl);
      const json = await response.json();

      /** 2回目以降は投稿データを取得 */
      if (isGetFeedObject == true) {
        for (const jsonFeedEntry of json.feed.entry) {
          /**
           * 今開いているページかどうか判定用データ
           *     jsonFeedEntry.id.$tで文字列を取得
           *     例 tag:blogger.com,1999:blog-1234567890123456789.post-0149374603246402764
           *     取得した文字列から正規表現でポストIDを取得
           * @type {Array} - ["0149374603246402764"]
           */
          const feedPostId = jsonFeedEntry.id.$t.match(/[0-9]+$/);
          /** サムネイルのurlを格納する変数を定義 @type {string} */
          let thumbnailUrl;

          /**
           * 今開いているページのポストIDと判定用IDが違っていたら処理を実行
           *     (今開いているページのデータは取得しない)
           *     判定用IDはfeedPostIdの0番目の要素を使用
           */
          if (currentPostId != feedPostId[0]) {
            /** サムネイルがある場合 */
            if ("media$thumbnail" in jsonFeedEntry) {
              /** サムネイルのurl @type {string} */
              thumbnailUrl = jsonFeedEntry.media$thumbnail.url;

              /**サムネイルのパラメーター @enum {string} 正規表現 */
              const thumbnailParameter = {
                parameter1: /s72-.*$/,
                parameter2: /\/s72-.*\//,
                parameter3: /default.jpg$/,
              };

              /** サムネイルのパラメーターを置き換えるif文のブロック */
              if (thumbnailParameter.parameter1.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter1,
                  thumbnailSetting.parameter1
                );
              } else if (thumbnailParameter.parameter2.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter2,
                  thumbnailSetting.parameter2
                );
              } else if (thumbnailParameter.parameter3.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter3,
                  thumbnailSetting.parameter3
                );
              }
            } else {
              /** サムネイルがない場合の代替画像 */
              thumbnailUrl = thumbnailSetting.noImage;
            }
          }
          for (const jsonFeedEntryLink of jsonFeedEntry.link) {
            if (jsonFeedEntryLink.rel == "alternate") {
              const feedObject = {};
              feedObject.title = jsonFeedEntry.title.$t;
              feedObject.url = jsonFeedEntryLink.href;
              feedObject.thumbnail = thumbnailUrl;
              objects.push(feedObject);
            }
          }
        }
      }

      /** 1回目は投稿数を取得 */
      if (isFirstFeed == true) {
        totalResults = json.feed.openSearch$totalResults.$t;
        /** 2回目以降は投稿数を取得しない */
        isFirstFeed = false;
        /** 2回目以降はフィードオブジェクトを取得する */
        isGetFeedObject = true;
      }
      createRandomStartIndex();
    } catch (error) {
      console.error("フィードの取得に失敗しました");
      errorHanding();
      return;
    }
  } while (objects.length < numberOfDisplay);
  /** createHtml関数を実行 */
  createHtml(objects);
}
createRandomObjects();
//]]>
</script>
<style>
.all-posts {
  min-height: 465px;
}
.all-posts__post-list-item-anchor {
  display: grid;
  grid-template-rows: 72px;
  grid-template-columns: 25% 1fr;
  grid-template-areas: "thumnail title";
}
.all-posts__post-list-item-img {
  grid-area: thumnail;
  width: 72px;
  height: 72px;
  object-fit: cover;
}
.all-posts__post-list-item-figcaption {
  grid-area: title;
}
/* https://projects.lukehaas.me/css-loaders/ */
.all-posts__loader {
  color: #808080;
  font-size: 12px;
  margin: auto;
  width: 1em;
  height: 1em;
  top: 100px;
  left: 0;
  right: 0;
  border-radius: 50%;
  position: relative;
  -webkit-animation: load4 1.3s infinite linear;
  animation: load4 1.3s infinite linear;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
}
@-webkit-keyframes load4 {
  0%,
  100% {
    box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
  }
  12.5% {
    box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  25% {
    box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  37.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em,
      0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  50% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em,
      0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  62.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
  }
  75% {
    box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
  }
  87.5% {
    box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
  }
}
@keyframes load4 {
  0%,
  100% {
    box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
  }
  12.5% {
    box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  25% {
    box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  37.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em,
      0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  50% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em,
      0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  62.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
  }
  75% {
    box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
  }
  87.5% {
    box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
  }
}
</style>

ひとつずつ表示するタイプ

ひとつずつ表示するタイプは要素をひとつずつDOMへ追加しています。

したがってその都度、画面の位置調整などの計算が発生する場合もあります。

パフォーマンス的にあまりよろしくない気がします(感想)

JavaScript | 複数のノードをまとめて追加(DocumentFragment)

ひとつずつ表示するタイプ:クリックすると開きます
<div id="allPostsLoader" class="all-posts__loader"></div>
<ul id="allPosts" class="all-posts"></ul>

<script>
/*! Copyright:2023 sutajp | Released under the MIT license | https://sutasutashiki.blogspot.com/p/mit-license.html */
//<![CDATA[
/** フィードですべての投稿をランダムに取得, HTMLで出力する */
"use strict";
/**
 * HTMLを生成して出力する関数
 * @param {Array<object>} objects - [{title:string, url:string}]
 * @param {number} numberOfDisplay
 */
function createHtml(objects) {
  /** サムネイル設定 @type { {[key: string]: number | string} } */
  const thumbnailSetting = {
    /** サムネイルの幅 @type {number} */
    thumbnailWidth: 72,
    /** サムネイルの高さ @type {number} */
    thumbnailHeight: 72,
    /** サムネイルの代替テキスト @type {string} */
    thumbnailAlt: "ランダムピックアップサムネイル",
  };

  /** HTMLの出力先 */
  const allPosts = document.getElementById("allPosts");
  for (const outputObject of objects) {
    let title = outputObject.title;
    /** タグなどをエスケープしてサニタイジング(無害化)する */
    if (/[<>"'`&]/g.test(outputObject.title)) {
      title = outputObject.title.replace(/[<>"'`&]/g, function (match) {
        return {
          "<": "&lt;",
          ">": "&gt;",
          '"': "&quot;",
          "'": "&#x27;",
          "`": "&#x60;",
          "&": "&amp;",
        }[match];
      });
    }
    /**
     * テンプレートリテラルで記述
     * {@link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals MDN}
     */
    const randomPostBody = `<li class="all-posts__post-list-item">
    <figure class="all-posts__post-list-item-figure">
    <a class="all-posts__post-list-item-anchor" href="${outputObject.url}">
    <img class="all-posts__post-list-item-img" width="${thumbnailSetting.thumbnailWidth}" height="${thumbnailSetting.thumbnailHeight}" alt="${thumbnailSetting.thumbnailAlt}" src="${outputObject.thumbnail}"/>
    <figcaption class="all-posts__post-list-item-figcaption">${title}</figcaption>
    </a></figure></li>`;

    allPosts.insertAdjacentHTML("afterbegin", randomPostBody);
  }
}

/**
 * フィードの取得に失敗したときに実行する関数
 */
function errorHanding() {
  const allPosts = document.getElementById("allPosts");
  allPosts.insertAdjacentHTML(
    "beforeend",
    "<span>データの取得に失敗しました&#x1f641;</span>"
  );
  /** ローディング画面にdisplay:noneを設定*/
  const allPostsLoader = document.getElementById("allPostsLoader");
  allPostsLoader.style.display = "none";
}
//]]>
/**
 * JSONデータを取得してオブジェクトを生成する関数
 */
async function createRandomObjects() {
  /** 現在開いているページ @type {number} */
  const currentPostId = "<data:view.postId/>";
  //<![CDATA[
  /** 表示数 @type {number} */
  const numberOfDisplay = 5;

  /** サムネイル設定 @type { {[key: string]: number | string} } */
  const thumbnailSetting = {
    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter1: "w72-h72-p-k-no-nu",

    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter2: "/w72-h72-p-k-no-nu/",

    /** Youtubeのサムネイル用 */
    parameter3: "mqdefault.jpg",

    /** 記事に画像がない場合の代替画像
     * @type {string}
     * {@link https://icooon-mono.com/11396-ウィンドウアイコン/ SVGを使用}
     * {@link https://icooon-mono.com/license/ ライセンス}
     * {@link https://heyallan.github.io/svg-to-data-uri/ Data URIにエンコード}
     */
    noImage:
      "data:image/svg+xml,%3Csvg id='_x32_' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='width: 128px; height: 128px; opacity: 1;' xml:space='preserve'%3E%3Cg%3E%3Cpath class='st0' d='M464,0H48C21.492,0,0,21.492,0,48v416c0,26.508,21.492,48,48,48h416c26.508,0,48-21.492,48-48V48 C512,21.492,490.508,0,464,0z M444.664,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S434.172,35,444.664,35z M374.164,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S363.672,35,374.164,35z M303.664,35 c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S293.172,35,303.664,35z M472,464c0,4.406-3.586,8-8,8H48 c-4.414,0-8-3.594-8-8V104h432V464z' fill='%234B4B4B'%3E%3C/path%3E%3Crect x='112' y='192' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='272' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='352' class='st0' width='152' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E",
  };

  /**
   * 取得したデータを格納
   * @type {Array<object>} - [{title:string, url:string}]
   */
  let objects = [];

  /**
   * 投稿数
   * @type {Number} - json.feed.openSearch$totalResults.$t
   */
  let totalResults;

  /** 投稿数の取得の可否条件 @type {Boolean} */
  let isFirstFeed = true;
  /** 投稿データの取得の可否条件 @type {Boolean} */
  let isGetFeedObject = false;
  /** 重複チェック用配列 @type {Array} */
  let duplicateCheck = [];
  /** startIndexの初期値 @type {number} */
  let startIndex = 1;
  const maxResults = 1;
  /** do while文 条件式用のカウント @type {number} */
  let count = 0;

  /** ランダムにstartIndexを作成する関数 */
  function createRandomStartIndex() {
    startIndex = Math.floor(Math.random() * totalResults + 1) + 1;
    duplicateCheck.push(startIndex);
  }
  do {
    /** 1回だけ重複チェック */
    if (duplicateCheck.includes(startIndex)) {
      createRandomStartIndex();
    }
    const feedUrl =
      "/feeds/posts/summary?alt=json&start-index=" +
      startIndex +
      "&max-results=" +
      maxResults;
    try {
      const response = await fetch(feedUrl);
      const json = await response.json();
      /** 2回目以降は投稿データを取得 */
      if (isGetFeedObject == true) {
        for (const jsonFeedEntry of json.feed.entry) {
          /**
           * 今開いているページかどうか判定用データ
           *     jsonFeedEntry.id.$tで文字列を取得
           *     例 tag:blogger.com,1999:blog-1234567890123456789.post-0149374603246402764
           *     取得した文字列から正規表現でポストIDを取得
           * @type {Array} - ["0149374603246402764"]
           */
          const feedPostId = jsonFeedEntry.id.$t.match(/[0-9]+$/);
          /** サムネイルのurlを定義 @type {string} */
          let thumbnailUrl;

          /**
           * 今開いているページのポストIDと判定用IDが違っていたら処理を実行
           *     (今開いているページのデータは取得しない)
           *     判定用IDはfeedPostIdの0番目の要素を使用
           */
          if (currentPostId != feedPostId[0]) {
            /** サムネイルがある場合 */
            if ("media$thumbnail" in jsonFeedEntry) {
              /** サムネイルのurl @type {string} */
              thumbnailUrl = jsonFeedEntry.media$thumbnail.url;

              /**サムネイルのパラメーター @enum {string} 正規表現 */
              const thumbnailParameter = {
                parameter1: /s72-.*$/,
                parameter2: /\/s72-.*\//,
                parameter3: /default.jpg$/,
              };

              /** サムネイルのパラメーターを置き換えるif文のブロック */
              if (thumbnailParameter.parameter1.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter1,
                  thumbnailSetting.parameter1
                );
              } else if (thumbnailParameter.parameter2.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter2,
                  thumbnailSetting.parameter2
                );
              } else if (thumbnailParameter.parameter3.test(thumbnailUrl)) {
                thumbnailUrl = thumbnailUrl.replace(
                  thumbnailParameter.parameter3,
                  thumbnailSetting.parameter3
                );
              }
            } else {
              /** サムネイルがない場合の代替画像 */
              thumbnailUrl = thumbnailSetting.noImage;
            }
          }
          for (const jsonFeedEntryLink of jsonFeedEntry.link) {
            if (jsonFeedEntryLink.rel == "alternate") {
              const feedObject = {};
              feedObject.title = jsonFeedEntry.title.$t;
              feedObject.url = jsonFeedEntryLink.href;
              feedObject.thumbnail = thumbnailUrl;
              objects.push(feedObject);
              /** createHtml関数を実行 */
              createHtml(objects);
              objects.length = 0;
              count++;
            }
          }
        }
      }

      /** 1回目は投稿数を取得 */
      if (isFirstFeed == true) {
        totalResults = json.feed.openSearch$totalResults.$t;
        /** 2回目以降は投稿数を取得しない */
        isFirstFeed = false;
        /** 2回目以降はフィードオブジェクトを取得する */
        isGetFeedObject = true;
      }
      createRandomStartIndex();
    } catch (error) {
      console.error("フィードの取得に失敗しました");
      errorHanding();
      return;
    }
  } while (count < numberOfDisplay);

  /** ローディング画面にdisplay:noneを設定 */
  const allPostsLoader = document.getElementById("allPostsLoader");
  allPostsLoader.style.display = "none";
}
createRandomObjects();
//]]>
</script>
<style>
.all-posts {
  min-height: 465px;
}
.all-posts__post-list-item-anchor {
  display: grid;
  grid-template-rows: 72px;
  grid-template-columns: 30% 1fr;
  grid-template-areas: "thumnail title";
}
.all-posts__post-list-item-img {
  grid-area: thumnail;
  width: 72px;
  height: 72px;
  object-fit: cover;
}
.all-posts__post-list-item-figcaption {
  grid-area: title;
}
/* https://projects.lukehaas.me/css-loaders/ */
.all-posts__loader {
  color: #808080;
  font-size: 12px;
  margin: auto;
  width: 1em;
  height: 1em;
  top: 100px;
  left: 0;
  right: 0;
  border-radius: 50%;
  position: relative;
  -webkit-animation: load4 1.3s infinite linear;
  animation: load4 1.3s infinite linear;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
}
@-webkit-keyframes load4 {
  0%,
  100% {
    box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
  }
  12.5% {
    box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  25% {
    box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  37.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em,
      0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  50% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em,
      0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  62.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
  }
  75% {
    box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
  }
  87.5% {
    box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
  }
}
@keyframes load4 {
  0%,
  100% {
    box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
  }
  12.5% {
    box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  25% {
    box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0,
      0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
  }
  37.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em,
      0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  50% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em,
      0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
  }
  62.5% {
    box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
  }
  75% {
    box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
  }
  87.5% {
    box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em,
      0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
  }
}
</style>

設置

HTML/JavaScriptガジェットを追加、プログラムを貼り付けて保存します。

レイアウト

ガジェットを追加をクリック

HTML/JavaScriptガジェットを追加

プログラムを貼り付けて保存をクリック

画面右下のフロッピーディスクマーク(保存)をクリック

設定

いくつか設定項目を設けました。

表示数

/** 表示数 @type {number} */
  const numberOfDisplay = 5;

サムネイルの設定

/** サムネイル設定 @type { {[key: string]: number | string} } */
const thumbnailSetting = {
  /** サムネイルの幅 @type {number} */
  thumbnailWidth: 72,
  /** サムネイルの高さ @type {number} */
  thumbnailHeight: 72,
  /** サムネイルの代替テキスト @type {string} */
  thumbnailAlt: "ランダムピックアップサムネイル",
};
  /** サムネイル設定 @type { {[key: string]: number | string} } */
  const thumbnailSetting = {
    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter1: "w72-h72-p-k-no-nu",

    /** サムネイルのパラメーターを置き換え @type {string} */
    parameter2: "/w72-h72-p-k-no-nu/",

    /** Youtubeのサムネイル用 */
    parameter3: "mqdefault.jpg",

    /** 記事に画像がない場合の代替画像
     * @type {string}
     * {@link https://icooon-mono.com/11396-ウィンドウアイコン/ SVGを使用}
     * {@link https://icooon-mono.com/license/ ライセンス}
     * {@link https://heyallan.github.io/svg-to-data-uri/ Data URIにエンコード}
     */
    noImage:
      "data:image/svg+xml,%3Csvg id='_x32_' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='width: 128px; height: 128px; opacity: 1;' xml:space='preserve'%3E%3Cg%3E%3Cpath class='st0' d='M464,0H48C21.492,0,0,21.492,0,48v416c0,26.508,21.492,48,48,48h416c26.508,0,48-21.492,48-48V48 C512,21.492,490.508,0,464,0z M444.664,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S434.172,35,444.664,35z M374.164,35c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S363.672,35,374.164,35z M303.664,35 c10.492,0,19,8.508,19,19s-8.508,19-19,19s-19-8.508-19-19S293.172,35,303.664,35z M472,464c0,4.406-3.586,8-8,8H48 c-4.414,0-8-3.594-8-8V104h432V464z' fill='%234B4B4B'%3E%3C/path%3E%3Crect x='112' y='192' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='272' class='st0' width='288' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3Crect x='112' y='352' class='st0' width='152' height='32' fill='%234B4B4B'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E",
  };

補足

ランダムポストが出力されるまでローディング画面を表示しています。

必要なければ以下を削除してください。

<div id="allPostsLoader" class="all-posts__loader"></div>

JavaScriptは2か所

/** HTMLを出力後, ローディング画面にdisplay:noneを設定*/
const allPostsLoader = document.getElementById("allPostsLoader");
allPostsLoader.style.display = "none";
/* https://projects.lukehaas.me/css-loaders/ */
以下、すべて削除

サムネイルのパラメーターの初期値は「人気の投稿ガジェット」のパラメーターw72-h72-p-k-no-nuに準じました。

好みのものに変更してください。

パラメーターについてはこちらが詳しいです。

Google/Blogger Image URL Parameters · GitHub

The Ultimate Guide to Customize and Edit Blogger (BlogSpot) Images



検索

お知らせ

カテゴリー

Random Picks

すたすた式

Enjoy!👍

QooQ