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 {
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`",
"&": "&",
}[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>データの取得に失敗しました🙁</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 {
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`",
"&": "&",
}[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>データの取得に失敗しました🙁</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
コメントなし: