画像を拡大できるようにしたいという欲求は前々からあった。この手のものでは Lightbox が有名だが、 jQuery べったりなのでいまさら使いたくない。 P_BLOG の頃は FacyZoom というやつを使っていて好きだったが、 2008 年から更新されていない。何かいいのがないかなと探していて Medium Zoom というのを見つけた。
ブログサービスの Medium の画像拡大機能のコピープラグインだ。 npm パッケージになっているので導入も楽だった。
実際のこんな感じ。

画像を拡大できるようにしたいという欲求は前々からあった。この手のものでは Lightbox が有名だが、 jQuery べったりなのでいまさら使いたくない。 P_BLOG の頃は FacyZoom というやつを使っていて好きだったが、 2008 年から更新されていない。何かいいのがないかなと探していて Medium Zoom というのを見つけた。
ブログサービスの Medium の画像拡大機能のコピープラグインだ。 npm パッケージになっているので導入も楽だった。
実際のこんな感じ。


画像にキャプションを表示させるようにした。 img 要素には alt 属性を細かく入れていたので alt の内容を抜き出して figcaption として表示させるようにした。やり方はまるっきり r7kamura さんの手法 を真似した。
昔の記事はレイアウトが壊れているかもしれないけど気にしないことにする。
先日 secondlife さんのブログについて触れたが、最近は r7kamura さんも頻繁にブログを書かれるようになった。
ブログハック的な記事を良く書かれていて読むのが楽しい。特に画像にキャプションを入れる手法が参考になった。
Markdown のパース & HTML への変換を Markdown パーサー任せにするのではなく、 html-pipeline.gem を使って良い感じに挟み込みたい処理を入れているようだ。
このブログでも結構ごちゃごちゃやっている。例えばコードスニペットのシンタックスハイライトがそうだ。 Markdown から HTML にしたあと Nokogiri で HTML を読み込んで解析し、コードスニペットをハイライトされた <code> ブロックに置換している。
これに加えて OGP 読み込み君も存在しているし、 Amazon Associate のタグを読み込んでアフィリエイトリンクに置換するための処理も存在している。
いまのところそれぞれ処理を Lokka プラグインとして実装しているので一つ一つが独立して動く必要があり、 Nokogiri による HTML の解析回数が無駄になっている。 r7kamura さん方式で、ルールだけそれぞれのモジュールで定義して、 HTML の解析と置換処理は 1 回で済むようにするのが効率的だと思う。
2010 年以降も自前でブログを運用してハックの様子をシェアしてくれている hail2u.net や lowreal.net はとても参考にしているし、勝手に同志のような感覚を抱いていた。自分も細々と頑張ってはいたが、ながしまきょうさん、 cho45 さんのほかには同じようにブログの運用に情熱を持っている人を見つけられなくて少しさみしい気持ちがしていたところ、 secondlife さんや r7kamura さんが加わってきて盛り上がってきた。 Blog Hacks 2020 の様相を呈してきている。
Flickr の Pro プランの有効期限が今月末で切れるので念のため全データを DL しておいた。自分は 2007 年に車上荒らしにあって PowerBook 17" を盗まれてしまい、そこに保存しておいたそれまでの人生のデジタル写真もすべて失ってしまった。一部は Flickr にアップロードしておいたので、それらを Mac 上に取り込むのが目的。 Flickr はちゃんとしていて、データの DL リクエストをすると一日くらいで画像だけでなく文章はコメント含めて全部 DL できるようになってる。 GDPR 便利。
何枚かの写真はアップロード時に縮小されてしまっていて、 5K のディスプレイで見るとかなりちっちゃい。試みに image upscale などでググってみると以下の Web サービスが見つかった。
最大 4 倍までアップスケールできる。利用にはアカウント登録が必要だった。無料っぽいので試してみた。アップロードしたのは以下の写真。この写真は 800×600 の解像度でしか落とせなかった。
それがこうなった。ぱっと見綺麗。
ただし拡大してみると欠点がないわけではなかった。こんな感じ。

暗い部分、光が反射している部分は綺麗にアップスケールできないみたいだった。機械学習モデルが進化するとこの辺も綺麗にアップスケールできるようになるのかな。
変換の方法はファイルを都度アップロードする方法のほかに、 Google Drive にファイルを置いてディレクトリ指定で一括変換したり、REST API も用意されていて、 curl や自分でプログラムを書いて変換させることも可能っぽい。なかなか便利そう。
なおこのサービスは無料で使えるのは 5 枚までで、それ以上はサブスクリプションか都度払いでお金がかかるシステムのようだ。
Flickr から落とした低解像度の写真が何枚かあるので都度課金かサブスクリプションの単月契約で利用してみようかなと思ってる。
外出自粛かつ自分自身にコロ助疑惑があったのでずっと部屋に閉じこもってサイトのデザインをいじってた。

上がこれまでで、左下が今後、右下が画像がワイドな場合のバージョン。これまで横幅 1280px 想定にして画像も横幅 1280px のサイズで表示するようにしていた。
ただしこれだと写真は見やすくても文字が読みにくい。人間の目は 1280px 繰り返し左右に移動させるのには適していないようだった( N=1 )。
そういうわけでまたまた cho45 さんのブログのレイアウトをパクって、文章は横幅短めに、写真はでかいサイズで表示するようにした。
文章部分の幅を 800px にして、画像を読み込んだときに一定の条件にマッチしたら写真の横幅を 1280px で表示するようにした。 margin-left: -250px; してるのがミソ。横幅 800px だと大分文章は読みやすいし、でかい写真は大きく見えて便利。
@media screen and (min-width: 1422px) {
#content #main article .body > p {
img[class~="large"] {
width: $content-max-width;
max-width: $content-max-width;
margin-left: -250px;
}
}
}JS はこんな感じのコードを書いた。
const checkImageSize = (target) => {
if (typeof target === 'undefined') {
return;
}
const width = target.naturalWidth;
const height = target.naturalHeight;
const isPhoto = RegExp('(lh3\.googleusercontent\.com|\.jpe?g$)').test(target.src);
if (width > 1279 && width > height && isPhoto) {
target.classList.add('large');
}
}
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.addEventListener('load', (event) => checkImageSize(event.target));
lazyImageObserver.unobserve(lazyImage);
}
});
});
const selectors = "#content article .body img, #content article .similar img";
const lazyImages = [].slice.call(document.querySelectorAll(selectors));
if ("IntersectionObserver" in window) {
for (let image of lazyImages) {
const src = image.src;
image.dataset.src = src;
if (lazyImages.indexOf(image) === 0) {
const promise = new Promise(resolve => resolve(image));
promise.then(checkImageSize).catch(setTimeout(checkImageSize, 100))
continue;
}
image.src = "";
};
lazyImages.forEach(lazyImage => {
lazyImageObserver.observe(lazyImage);
});
}画像の読み込みが起こったタイミングにフックして画像のサイズチェックを行い、条件にマッチしたら img タグに class を追加し、 CSS のメディアクエリと合わせ技で大きく表示するようにしている。以前やった画像の遅延読み込みのコードを改良した。
その他、 HTML 5 対応が中途半端だったのでマークアップを見直して、適度に <header> や <secition> 、 <article> 、 <footer> を使ってマークアップし直した。 このせいだと思うけど Google Adsense の自動広告が差し込まれる位置が変わった。これまでヘッダーの下に入り込んでいたやつが <header> と <article> の間に入るようになった。
今後も外出禁止が続いたらひたすら自分のブログをいじってしまう予感。こういうエネルギーを仕事とか個人サービス開発とかに当てられるとよいのだろうけどなかなかそういう方向には向かない。

各記事の一枚目の画像をカバー画像とみなすようにして、関連記事にサムネイルを表示するようにしてみた。画像があるだけで記事をクリックしてみたい感が高まると思う(残念ながら Google Analytics で見る限り直帰率は改善してない)。人間は、文字だけを読むよりもイラストや画像など視覚的な情報を一緒に見ることで物事の理解度が高まると本で読んだ。自分で過去記事を読み直していても画像があると関連記事をクリックしてみたくなるし、満足感のある改修だった。
なおカバー画像の判定処理は記事本文を読み込んだ上で正規表現で調べているので多分重い(ベンチマークを取ったらタイトルを表示する処理に比べて二倍くらい遅かった)のだけど、関連記事表示部分はキャッシュしてるのでそんなに体感速度は悪化してないはず。

写真や画像があると文章だけよりも記憶に定着しやすいらしい。なのでヘッダー画像のバリエーションを増やしてみることにした。これまで自分が撮ってきた写真の中で気に入ってるもの、ヘッダー画像にちょうど良さそうなやつを 14 枚選んで、これまで表示していた門司の「平民食堂」のやつと合わせて 15 枚をランダムピックアップして表示させている。本当ならランダムではなく記事ごとにイメージに合う画像を選定すべきなのだろうけどそこまではやってない。「このブログ主はこういう写真の場所に行ったりこういう写真をいいと思ってるんだな」ということが伝われば良いと思ってる。
ランダム表示に関して、最初は JavaScript で CSS の background-image を書き換える方式でやっていたが、 DOM の読み込み完了時に動く都合上、かくつきが出てしまう。それで画像の選定は Ruby で行って data-attribute として指定し、予め CSS にそれぞれの data-attribute に応じた background-image を書いておくことにした。これでかくつきはほぼほぼなくなった。
<div id="header" data-background-image="heimin">
</div>#header[data-background-image="heimin"] {
background-image: url(header-bg-heimin.jpg);
}このほかにも CSS 周りを結構いじってて、パンくずリストが載る部分は文字が読みにくくなるのでグラデーションでシャドーを掛けてる。これまで float でやってた配置の制御を flexbox に置き換えたりもやった。今のデザインのベースは 10 年前に作ったので、当時はグラデーションなんかは CSS で実現できず横幅 1px の画像をリピートする方法を使ったが、これで画像を捨てて全て CSS で実現できそう。良い世の中になってきてる。