Bloggerの記事/追加ページの投稿画像にloading-lazyを自動的に付けるプログラム

自分の写真結城永人 -

以前、取り上げた画像の遅延読み込みのloading-lazyをBloggerの記事/追加ページの投稿画像に自動的に付けるJavaScriptプログラムを開発したので、無料で提供する。

画像のある記事/追加ページの本文を全てJavaScriptで止めてから書き出すことになるので、その分だけ表示速度が遅れる。そしてHTMLだけの場合と比べてGoogle検索などの読み取りが全く劣らないとはかぎらない。年々、向上して今では殆ど差がないといわれるから大丈夫かも知れないけれども念のために注意しておきたい。デザインは変わらず、表示速度が速くなる分は有利(画面外の画像が多いほどに遅延読み込みの効果が大きい)だとしてもGoogle検索などからサイト評価が上がって訪問者が増えるかどうかは分からないので、ブログのアクセスアップにSEO(検索エンジン最適化)対策を考える場合は様子を見ながら取り入れるべきだろう。

参考:JavaScript SEO の基本を理解する

もう一つサイト広告のアドセンスの自動広告を使う場合にアドセンスの管理画面のプレビューに反映しないみたいだ。サイト広告が設定されていればブログで実際に表示されるけれどもアドセンスの管理画面で数や位置を決めるときはサイト広告のプレビューが出ないと分からないからloading-lazyの自動化のプログラムを外す必要がある。

使う人は制作者の結城永人の著作権の表記もソースコードに併せて載せておくMITライセンスでカスタマイズも再配布も許可するから消さないで欲しい。

僕が提供している非公式テーマのImaginaryを使っている場合は著作権の表記が含まれているから今回の分を繰り返す必要はない。

カスタマイズは全てBloggerの管理画面のテーマのHTML編集、またはバックアップと元に戻すからテンプレートのソースコードを書き換える。

追記:HTMLのloading-lazyは2022年3月時点では投稿画像の遅延読み込みの十分な効果が得られないかも知れないので、JavaScriptのIntersection Observer APIを使った確実な方法も紹介している。

投稿画像にloading-lazyを付けるソースコード

ブログのポップアップに「lazy」と表示されている

初回画面の投稿画像を除外して記載する

記事/追加ページの全ての投稿画像で、初回画面のものを除外して画像のimgタグに遅延読み込みのloading-lazyを付ける。

HTMLとJavaScriptの二つのソースコードを使用する。

Bloggerのテンプレートの種類によって違いが出て来る場合があって取り付ける際は幾らか修正しなくてはならないかも知れない。

HTML

<div class='post-body'>
  <b:tag cond='data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/></b:tag>
</div>

※太字が追加部分。

Bloggerの全てのテンプレートで記事/追加ページの投稿データが独自タグの<data:post.body/>で出されるので、所定のnoscriptタグで囲む。

するとブラウザのJavaScriptがオフのときは通常の仕方で投稿が表示され、オンのときは投稿全体の表示が、一旦、止められることになる。

親要素や周りの状況はテンプレートによって異なるかも知れないけど、とにかく必要なのは親要素のクラスで、サンプルだとdivタグの「post-body」をJavaScriptの方と合わせて使っている。

noscriptタグは独自タグの条件分岐のifでBloggerの通常の投稿画像、すなわちYouTube動画で取得可能なサムネイル以外があるときに書き出すようにした。なので通常の投稿画像がないときはnoscriptgタグはなくてブログの記事/追加ページのソースコードと従来のままの状態になる。

JavaScript

/* Copyright: Nagahito Yuki 2022 | https://www.nagahitoyuki.com/2022/01/automatic-lazy-load-program-for-post-and-page-images-on-blogger.html | License: The MIT License */
const psbd = Blog1.querySelector("div.post-body"), blct = psbd.querySelector("noscript.blog-content"), npsbd = psbd.cloneNode(false);
npsbd.insertAdjacentHTML("afterbegin", blct.innerText);
const blimgs = npsbd.querySelectorAll("img");
if (blimgs) {
Promise.all([...blimgs].map(blimg =&gt; {
const aroi = blimg.dataset.originalWidth / blimg.dataset.originalHeight;
if (blimg.height) {
if (!blimg.width &amp;&amp; aroi) blimg.width = blimg.height * aroi; } else if (blimg.width) {
if (aroi) blimg.height = blimg.width / aroi; } else if (aroi &amp;&amp; !blimg.style.aspectRatio) {
blimg.style.aspectRatio = aroi; }
blimg.dataset.src = blimg.src; blimg.src = ""; return blimg; })).then(results =&gt; {
psbd.parentNode.replaceChild(npsbd, psbd); results.forEach(result =&gt; {
if (result.getBoundingClientRect().top &gt;= window.innerHeight) result.loading = "lazy";
result.src = result.dataset.src; delete result.dataset.src; }); }); } else {
psbd.parentNode.replaceChild(npsbd, psbd); }

※冒頭のコメント(/* ... */)はソースコードの著作権の表記。

初回画面の投稿画像を除外するために投稿画像のimgタグの属性を使って本文の中での表示位置(最上段からの高さ)を計測している。画像のサイズを知るためにBloggerの画像タグで自動的に付けられるheight(縦幅)かwidth(横幅)かdata-original-height(オリジナル画像の縦幅)とdata-original-width(オリジナル画像の横幅)を使っている。なのでそれらの属性が外されている正確に動作しなくなる。

注意:投稿画像のimgタグのheight属性か少なくともdata-original-height属性とdata-original-width属性(両方)を必ず付ける(Bloggerの投稿画像でどれかは付けられるものだから外さないでおく)。

画像の表示速度は画像タグにwidth属性とheight属性があった方がブラウザが縦幅比を直ぐに知るから良いといわれる。Bloggerでは「元のサイズ」を選ぶと付かないので、付けて構わなければJavaScriptの「blimg.style.aspectRatio = aroi;」の後に「blimg.height = blimg.dataset.originalHeight; blimg.width = blimg.dataset.originalWidth;」を追加するとheight属性とwidth属性が付いてないときにオリジナル画像のサイズのdata-original-height属性とdata-original-width属性から取り込んで追加することができる。ただしそのままでは投稿画像が表示領域からはみ出すかも知れないので、画像タグをデザインのCSS.post-body img {max-width:100%}YouTube動画も含める場合.post-body img, .post-body iframe {max-width:100%})などでどのくらいの大きさまで表示するかを調整する必要がある。

投稿画像が「元のサイズ」以外で取り込まれるときはheight属性かwidth属性が付くので――長い方が選ばれる――付いてない方をdata-original-height属性とdata-original-width属性の比率から算出して付けるようにしてある。

ブログの記事/追加ページの高速化の方法として投稿画像のwebpファイルへの軽量化も可能で、遅延読み込みと組み合わせるプログラムも掲載したから一緒に使うともっと効果的だと思う。

追記:通常の使用では問題がないけれども画像の横並びを二段(縦二列)以上で行ったときにはみ出してレイアウトが崩れる不具合が確認された。

発見された事例ではグリッドレイアウトの列指定のgrid-template-columnsの「1fr 1fr」を「50% 50%」に変えたら大丈夫だったので、分割する単位を「%」にすれば画像がはみ出さなくなると考えられる。

ただしグリッドレイアウトに「%」を使うと隙間のgapの値が相殺されなくなるので、gapを付けるときは横幅や縦幅に加味しないとずれてしまう。計算式のcalc()を使うと合わせることができる。例えばgrid-template-columnsが「50% 50%」のグリッドレイアウトで「1em」のgap付けるときはその値をセルの数で割ってそれぞれのサイズから引く。すると「calc(50% - 0.5em) calc(50% - 0.5em)」というマークアップになる。親ボックスの全体の100%(50% + 50%)に隙間の「1em」が収まるようになるからデザインは崩れない。

もう一つ画像のはみ出しを切り捨てる方法でも大丈夫だった。

修正するには投稿画像の親要素にCSSのoverflow:hiddenを指定する。

プログラムに組み込むならばnpsbd.insertAdjacentHTML("afterbegin", blct.innerText);の後にnpsbd.style.overflow = "hidden";を追加する。

CSSのoverflow:hiddenを使うと子要素のmargin(外側の余白)が親要素からはみ出さなくなる、いい換えると親要素の内側の余白のように収まるので、それによってブログの既存のデザインが崩れる場合は個別に調整する必要がある。

関連:Bloggerの投稿画像の遅延読み込みのloading-lazyのWebpPのrwの自動化のJavaScriptプログラムの説明付きソースコード

投稿のYouTube動画にもloading-lazyを追加する場合

Bloggerの投稿から簡単に取り込めるYouTube動画のiframeタグもloading属性で遅延読み込みが可能なので、やっても良いと思う。

基本のソースコードのJavaScriptの二ヵ所を変更する。

※cond属性の「and not data:view.featuredImage.isYoutube」を削除する。

変更①JavaScript

const blimgs = npsbd.querySelectorAll("img, iframe");

※太字が追加部分。

変更②JavaScript

if (blimgs) {
Promise.all([...blimgs].map(blimg => {
  (中略)
if (result.getBoundingClientRect().top &gt;= window.innerHeight) result.loading = "lazy";
result.src = result.dataset.src; delete result.dataset.src; }); }); } else {
psbd.parentNode.replaceChild(npsbd, psbd); }

※打ち消し線が削除部分。

Bloggerの投稿からの取り込み、またはYouTube動画の埋め込みコードも普通に使うかぎりはブログで初回画面で表示されないときに遅延読み込みが発動する。

注意:初回画面でのYouTube動画の有無を判定するためにiframeタグのheight属性とwidth属性を使っているので、外してしまうと正常に動作しない。

Bloggerの投稿でYouTube動画をメニューのボタンから取り込むとheight属性とwidth属性が付くし、YouTube動画の埋め込みコードでも付いているのを外さないで使う。

iframeタグを遅延読み込みの対象するからYouTube以外のSoundCloudやVimeoといった他のソーシャルメディアの埋め込みコードも含まれることになる。

YouTubeだけとか遅延読み込みを特定のiframeにかけたい場合はそうした要素にidかクラスを付けて一つ目の追加部分の「iframe」を「iframe.youtube」(動画のiframeタグにyoutubeのクラスを付けた場合)のように書きき換えれば良い。

初回画面の投稿画像にdecoding-asyncも追加する場合

ブログのポップアップに「async」と表示されている

初回画面の投稿画像を少しでも早く出すためにdecoding-asyncを付けることができる。

基本のソースコードのJavaScriptの一ヵ所を変更する。

JavaScript

results.forEach(result =&gt; {
if (result.getBoundingClientRect().top &gt;= window.innerHeight) result.loading = "lazy"; else result.decoding = "async";
result.src = result.dataset.src; delete result.dataset.src; });

※太字が追加部分。

投稿画像のimgタグだけにloading-lazyを記載する基本のソースコードと投稿動画などのiframeタグにも追加するものとどちらでも同じところを編集する。

遅延読み込みでYouTube動画に対応しているとdecoding属性がiframeタグに無効だから付けないようにプログラムから除外した方が良いと思う。

JavaScript

results.forEach(result =&gt; {
if (result.getBoundingClientRect().top &gt;= window.innerHeight) result.loading = "lazy"; else if (result.tagName === "IMG") result.decoding = "async";
result.src = result.dataset.src; delete result.dataset.src; });

※太字が追加部分。

画像とYouTube動画にloading-lazyを付けて初回画面の画像だけdecoding-asyncを付ける場合はYouTube動画からdecoding-asyncを除外する、いい換えれば画像だけしか付けないようにする。

本文に記載されたscriptタグを実行するソースコード

投稿のHTMLモードで、記事/追加ページの本文にJavaScriptが置かれる場合がある。自作のコードだけではなくてTwitterやInstagramなどのWebサービスの埋め込みコードなどにも含まれている。noscriptタグで止められるとそのままでは実行できないから修正用のプログラムを追加しなくてはならない。

JavaScript

const pbscs = npsbd.querySelectorAll("script");
if (pbscs) {
pbscs.forEach(pbsc =&gt; {
const npbsc = document.createElement("script");
if (pbsc.src) npbsc.src = pbsc.src;
if (pbsc.async) npbsc.async = "async";
if (pbsc.textContent) npbsc.textContent = pbsc.textContent;
npsbd.appendChild(npbsc); }); }

遅延読み込みの基本のソースコードに続けて同じscriptタグの中に入れておく。

ブログの投稿の一つでも本文にscriptタグを載せていたら追加しないと実行されない。

TwitterやInstagramなど埋め込みコードの場合はファイルの読み込みが通常よりも遅れるので、コンテンツが表示されるのにもっと時間がかかってしまうけれどもブログの初回画面が余計に遅らされることは逆になくなるので、全体として大きな差はないかも知れない。

プログラムの動作確認用のソースコード

投稿画像や動画にloadingやdecoding属性を付けるプログラムはブログに取り入れてはっきり分かるものではない。表示速度が上がるとしても体感される速さはほんの僅かだったり、インターネットの接続状況でも変わってしまうから気付かないかも知れない。

ちゃんと動作しているかどうかを確認するだのソースコードを載せておく。

JavaScript

if (result.getBoundingClientRect().top &gt;= window.innerHeight) result.loading = "lazy";
alert("loading属性は\u300c" + result.getAttribute("loading") + "\u300dの状態です\u3002\ndecoding属性は\u300c" + result.getAttribute("decoding") + "\u300dの状態です\u3002");
result.src = result.dataset.src; delete result.dataset.src; });

※太字が追加部分。

投稿の画像や動画を上から(横並びがあれば左を先に)一つずつ調べてloading属性とdecoding属性の有無をポップアップで表示する。

プログラムが正常に動作して初回画面にない画像や動画にloading属性が付けば「lazy」、それ以外で付かなければ「null」、初回画面にある画像にdecoding属性が付けば「async」、それ以外で付かなければ「null」と分かる。

暗くて見辛いけれども背景に何もない状態でポップアップが出たら元の画像が読み込まれる前に適切に変更されたことになる。

サイトのパフォーマンスをチェックするPageSpeed Insightsでは「オフスクリーン画像の遅延読み込み」が合格するようになる。

ただしPageSpeed Insightsはブラウザが元の投稿画像を読み込み始めた後にloading-lazyを付けても合格になってしまうからその結果だけではプログラムの動作確認として十分ではない。

Bloggerの投稿画像のプログラムの組み込み方

記事/追加ページの投稿データの独自タグは全て同じで、そして追加するJavaScriptの内容も殆ど同じだ。大きく変わるのは独自タグの親要素や周りの状況で、それに合わせて追加するJavaScriptの内容も少し変わり得る。

公式と非公式の全てのテーマでプログラムを組み込む際の共通の手順をサンプルのソースコードに従って挙げる。

  1. 投稿の本文の独自タグ:<data:post.body/>に「blog-content」のクラスのnoscriptタグを付ける(サンプルでは画像ありの記事/追加ページでしかnoscriptタグを付けないように加工されている)。
  2. 本文のnoscriptタグだけを含む親要素(他のコンテンツは含めても表示されなくなる)に「post-body」のクラスを付ける。
  3. JavaScriptの冒頭のpsbdの取得先の「div.post-body」を本文のnoscriptタグの親要素のタグとクラス、blctの取得先の「noscript.blog-content」をnoscriptタグとクラスに合わせる(サンプルでは合っているからそのままで構わない)。
  4. JavaScriptのソースコードを適切に読み込まれるところ(BloggerのHTMLの基本構造)、最も遅れずに本文を表示させるならばnoscriptタグを含む親要素の直後に置く。

テンプレートで記事/追加ページのの本文の独自タグがインデックスページ(トップやアーカイブやラベル)と共用になっていると遅延読み込みのソースコードがそのままでは不具合が生じるから記事/追加ページのみで使うように書き換える必要がある。

最も簡単なのはHTMLのソースコードの独自タグのtagのcond属性にバージョン毎の振り分け項目を追加する。

テンプレートのバージョンによる相違点について

現在、Bloggerのテンプレートはバージョンが二つに分かれていて公式テーマだとContempoなどのバージョン3とSimpleなどのパージョン2のテンプレートがある、それ以外の非公式テーマも、大抵、どちらかに含まれる。テンプレートのバージョンはレイアウトバージョンで、ソースコードの冒頭のhtmlタグで確認することができる。

レイアウトバージョン3
b:layoutsVersion='3'
レイアウトバージョン2
b:version='2'

使える独自タグなど、ソースコードがそれぞれの組で似ているので、レイアウトバージョンが同じものは公式でも非公式でもカスタマイズの参考になる。

バージョン3での記事/追加ページと画像ありの条件で振り分け

HTML:b:tag

<div class='post-body'>
  <b:tag cond='data:view.isSingleItem and data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/></b:tag>
</div>

※太字が追加部分。

HTML:script

<b:if cond='data:view.isSingleItem and data:view.featuredImage'>
<script>
  遅延読み込み用ソースコード
</script>
</b:if>

※太字が追加部分。

バージョン2での記事/追加ページと画像ありの条件で振り分け

HTML:b:tag

<div class='post-body'>
  <b:tag cond='data:blog.pageType == "item" or data:blog.pageType == "static_page" and data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/></b:tag>
</div>

※太字が追加部分。

HTML:script

<b:if cond='data:blog.pageType == "item" or data:blog.pageType == "static_page" and data:view.featuredImage'>
<script>
  遅延読み込み用ソースコード
</script>
</b:if>

※太字が追加部分。

遅延読み込みのHTMLが記事/追加ページだけでテンプレートのソースコードに記載されるようになる。

遅延読み込みのプログラムの組み込みの例として公式テーマを二つのバージョンに分けて紹介する。

Contempoなどのバージョン3の公式テーマ

  • Contempo
  • Soho
  • Emporio
  • Notable
  • Essential

全て共通のカスタマイズ

HTML

<b:includable id='postBody' var='post'>
  <!-- If metaDescription is empty, use the post body as the schema.org description too, for G+/FB snippeting. -->
  <div class='post-body entry-content float-container' expr:id='"post-body-" + data:post.id'>
    <b:tag cond='data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/><b/tag>
  </div>
<b:if cond='data:view.featuredImage'>
  <script>
    遅延読み込み用ソースコード
  </script>
</b:if>
</b:includable>

※太字が追加部分。

NotableだけはHTMLの同じコードが二つあって一つはウィジェットの設定のdefaultmarkupsタグの中なので、その外で実際に使われているBlog1のwidgetタグの中のHTMLを書き換えなくてはならない。

scriptタグはBloggerのXMLファイルでエラーにならなければどこでも構わないけど、しかしnoscriptタグを含む親要素の直後に置くことで最も遅れずに本文を表示させられる。

シンプルなどのバージョン2の公式テーマ

  • Simple
  • Dynamic Views
  • Picture Window
  • Awesome Inc.
  • Watermark
  • Ethereal
  • Travel

全て共通のカスタマイズ

HTML(デスクトップ)

<div class='post-body entry-content' expr:id='"post-body-" + data:post.id' expr:itemprop='(data:blog.metaDescription ? "" : "description ") + "articleBody"'>
  <b:tag cond='data:blog.pageType == "item" or data:blog.pageType == "static_page and data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/></b:tag>
  <div style='clear: both;'/> <!-- clear for photos floats -->
</div>
<b:if cond='data:blog.pageType == "item" or data:blog.pageType == "static_page" and data:view.featuredImage'>
  <script>
    遅延読み込み用ソースコード
  </script>
</b:if>

※太字が追加部分。

YouTube動画に対応するときはソースコードが少し違うけれども独自タグのtagのcond属性から「and not data:view.featuredImage.isYoutube」を削除することは変わらない。

HTML(モバイル)

<div class='post-body entry-content' expr:id='"post-body-" + data:post.id' itemprop='articleBody'>
  <b:tag cond='data:view.featuredImage' class='blog-content' name='noscript'><data:post.body/></b:tag>
  <div style='clear: both;'/> <!-- clear for photos floats -->
</div>
<b:if cond='data:view.featuredImage'>
  <script>
    遅延読み込み用ソースコード
  </script>
</b:if>

※太字が追加部分。

HTMLのカスタマイズがデスクトップとモバイルに分かれているので、必要な場合は両方とも投稿データの独自タグを所定のnoscriptタグで囲む。

scriptタグはBloggerのXMLファイルでエラーにならなければどこでも構わないけど、しかしnoscriptタグを含む親要素の直後に置くことで最も遅れずに本文を表示させられる。

コメント