ChromeがNative lazy loadingをサポートするようになったので、僕のblogでもこれを使おうと作ってみたgemになります。
Chrome v76から、Native lazy loadingがサポートされるようになりました。
Native lazy-loading for the web
ところで僕のblogは、本文をMarkdownで記述したものをHTMLに変換しています。Markdown記法で画像を挿入した場合に、どのようにloading属性を付与すればいいのでしょうか。Markdown内にHTMLを記述した場合、それはそのまま出力されるので、手でタグを書くことで実現できますが、果たしてそのようなことをしたいでしょうか。面倒です。
なので、Markdownの変換過程に介入することで解決できないだろうかと思い、調べてみました。
僕のblogでは、Markdown engineとしてRedcarpet gemを使用しています。そして、RedcarpetにはCustom Rendererという仕組みがあります。これで、![alt text](src)
の変換に介入します。
Railsでカスタムmarkdownを実装する - k0kubun’s blog
class CustomRenderer < ::Redcarpet::Render::HTML
def image(link, title, alt_text)
"<img loading=\"lazy\" src=\"#{link}\" alt=\"#{alt_text}\" />"
end
end
上記のようなコードで、 に loading attrを付与することができます。そして、それをgemにしたのがこれです。
実際には loading 属性には lazyの他にも autoとeagerを指定できるので、gemでもRendererをそれぞれ使い分けることでlazy以外を指定できます。 (なのでgem名にlazyと入れたのはちょっと失敗だったかもしれない)
ここまでが表参道.rb #51 での発表内容になります。
ところで、この手法は上手くいきませんでした。実際にこのgemを使用してblogをdeployすると、画像が全てリンク切れとなってしまったので、急いでrevertしてdeployをし直しました。
この原因は、Middleman側の設定である asset_hash
を有効にしていたためです。この設定によって尊重されるべきMiddleman側のCustom Rendererを自分のgemで上書いてしまっていたために、画像のsrcが正しくないものになってしまっていました。
asset_hash
を有効にしたまま loading="lazy"
をするには、もう変換後のHTMLを編集するMIddleman pluginを作成するしかなさそうです。
という訳で、after_build
のタイミングで以下のようなコードを実行するようなgemを作成しました。
def after_build(builder)
files = Dir.glob(File.join(app.config[:build_dir], "**", "*.html"))
files.each do |file|
contents = File.read(file)
replaced = contents.gsub(%r[<img], "<img loading=\"#{options[:loading]}\"")
File.open(file, 'w') do |f|
f.write replaced
end
end
end
このコードは、build directory 以下に存在する .html
ファイルについて、全ての <img>
タグに対して loading 属性を付けて上書き保存します。
ここまでが表参道.rb #52 での発表内容になります。
<img>
を除外するこの実装では、 pre や code の内部の img にも loading 属性を付与してしまいます。これはどうやって解決するか悩んだのですが、Nokogiriによって親に pre 、 code 、blockquote がある img に関しては属性を編集しない、というように処理を変更しました。
これで、ようやく僕のblogでlazy image loadingが実現できました。といってもChrome v76以降のみですが。
ということで、Starやpull reqなど頂けると大変ありがたいです。