rehype-plugin-auto-resolve-layout-shift を利用した Markdown ドキュメントにおける Layout Shift の解消
このブログは @nuxt/content で運用しており、執筆環境としては非常に快適に利用できている一方で、Markdown ベースの宿命として、Markdown 記法で画像を貼り付けている限り Layout Shift が発生するという課題が存在していました。
暫定的な対処としてしばらく <img> で置き換えてきましたが、根本的に対処するため、連休を活かして rehype-plugin-auto-resolve-layout-shift を開発しました。
この記事では、かんたんにその報告と紹介をしたいと思います。
Markdown の Layout Shift について
Markdown は #
で h1~h6 を表現できたり、 [text](href)
でリンクを貼れたり、 ![alt](src)
で画像が表示できたり何かと便利なフォーマットである一方、そのパーサやトランスパイラには、現代的な設定がデフォルトではついていないことがほとんどです。
例えば今なら <a>
リンクの href が外部なら自動的に rel="noopener"
が付与されてほしいですし、<img>
には自動で loading="lazy"
が付与されていてほしいところではないでしょうか。
以前もこういった課題を解消するために rehype-plugin-image-native-lazy-loading を開発して Lazy Loading を実現するなどしていましたが、冒頭に言及したとおり、 Layout Shift は改善できていませんでした。
Layout Shift の解消は Google の Core Web Vitals にも含まれている重要指標の一つですし、可能な限り対処しておくべき要件です。
ただ一方で、 Markdown 記法の気軽さも失いたくはないため、 Node.js のデファクトの Markdown/HTML ライブラリである rehype のプラグインとして開発・提供することとしました。
rehype-plugin-auto-resolve-layout-shift について
https://github.com/potato4d/rehype-plugin-auto-resolve-layout-shift
rehype-plugin-image-native-lazy-loading は、事前に画像のバイナリを取得することによって、画像の width/height をビルド時に計算するライブラリです。
Markdown が HTML に変換されるタイミングで画像のの指し示す先へとアクセスし、取得した結果に応じた width/height 属性を付与した <img>
へと変換します。
rehype プラグインであるため、remark/rehype を利用しているエコシステム(や、それを間接的に利用している markdown-it などのエコシステム)であれば利用できるため、Nuxt.js や Gatsby などの一般的な SSG 全般で利用可能です。
なお、現在このブログ( https://d.potato4d.me/ )では全ての記事においてこのプラグインが適用されており、適切な width/height が設定されている状態となっています。
利用方法
とりあえずで導入できる ratio モードと、最大のパフォーマンスを得るための maxWidth モードがあります。ratio モードの場合は設定が不要であるため、ここでは推奨かつひと手間必要である maxWidth モードのみ紹介します。
maxWidth モードは、利用先のコンテナ幅を明示的に指定することで、アスペクト比を維持したままベストな解像度で画像を表示します。
有効化する際は、maxWidth にはコンテナ幅を設定し、type に ‘maxWidth’ を設定します。@nuxt/content
の場合、 nuxt.config.js に以下のように記述します。
const rehypePlugins = [
// plugins config,
]
if (process.env.NODE_ENV === 'production') {
rehypePlugins.push(
['rehype-plugin-auto-resolve-layout-shift', { type: 'maxWidth', maxWidth: 768 }]
)
}
export default {
// ...
content: {
markdown: {
rehypePlugins
}
},
// ...
}
このように設定した場合、このプラグインは以下のような処理を行います。
1. width <= maxWidth の場合
この場合小さな画像を表示しているなどのケースが考えられるため、単純に元々の画像の width/height を尊重します。
例えば width=200,height=150 の場合などがこれに該当します。
2. width > maxWidth の場合
本題です。
Web の世界では基本的にコンテナ幅の width が指定されているはずであるため、このシチュエーションが意味することは、つまりはコンテナ幅を超過している状態であるということです。
例えば width=1600,height=900 の 16:9 の画像を想定します。
maxWidth は maxWidth/width を割り出して、 width/height にそのレートをかけます。つまりは width/height は以下の形となります。
- width = width * (maxWidth/width)
- height = height * (maxWidth/width)
今回の場合、 maxWidth/width = 0.48
なので、 width は 768 に、height は 432 となり、アスペクト比を維持した状態で最適なサイズへと変換されます。
実際に 768 での設定はこのブログで行っており、以下のように自動算出された値が属性値として挿入されています。
利用上の注意
- 内部的にはネットワークアクセスを介して画像を取得しているため、多少処理に時間がかかります。
そのため、 minify などと同様に、プロダクションビルド時に限定して有効化することを推奨します。
- 対応フォーマットについて
内部的に Jimp に依存しているため、現状 Jimp の対応フォーマットに依存しています。需要があれば、より広いフォーマットの対応も行う予定です。
おわりに
Markdown 製のブログや SSG が身の回りで更に増えているので、この辺りのプラグインがより有効に使われると良いなと。
余談ですが、このプラグインを作るきっかけになったのは、以下のツイートがきっかけでした。
Special thanks! @yamanoku さん