1. はじめに
昨日の記事では、Web Components を使って複数のコンポーネントを 1つのWebページ上に作成する方法を紹介しました。
今回は、そこで紹介したコードを改善します。
2. 改善点その1(CLS)
まず、CLS (Cumulative Layout Shift) の問題に対処します。
昨日の内容の場合、Webページがレンダリングされる前の時点で、Web Component の表示サイズを計算するための情報がないために、必要なサイズが確保されないまま最初のレンダリングが行なわれてしまいます。そしてその後、Web Component が画面上に適用される段階になってはじめて表示サイズが決定するため、「今まで何もなかった場所に、Web Component が表示され、その下の要素の位置がずれる」現象が発生してしまいます(これが CLS です)。
この問題に対応するには、カスタム要素の幅と高さを CSS で指定すればよいのですが、カスタム要素に対して width と height を指定しても効かないので、カスタム要素をラッパー要素で囲んでおき、ラッパー要素の方に大きさを指定しておきます。
HTML
<div class="wrapper">
<my-element></my-element>
</div>
CSS
div.wrapper {
width: 400px;
height: 225px;
}
aspect-ratio プロパティを使ってもよいです。
div.wrapper {
width: 400px;
aspect-ratio: 16 / 9;
}
3. 改善点その2(Web Component の遅延読み込み)
次に、ファーストビュー(最初に表示される画面の範囲)より下にある Web Component を、必要になったとき(画面がスクロールされたとき)になってはじめて読み込ませることで、Webページアクセス時の転送データ量を少なくすることができます(<img>
要素の loading=lazy
属性と同じことをさせるイメージです)。
但し、ファーストビュー上に表示される(可能性のある)Web Component を遅延読み込みすると、LCP (Largest Contentful Paint) というパフォーマンス指標の値が悪化する可能性があります。簡単に言うと、アクセス時の画面表示が遅くなるかもしれない、ということです。ですので、厳密にはそちらは遅延読み込みさせない方がよいです。とはいえ、たいして影響がないのならまとめて遅延読み込みでもよいと思います。
以下が、遅延読み込みのサンプルコードになります。遅延せずに JavaScript ファイルをインポートするコードは削除します(ファーストビュー上の Web Component を表す JavaScript ファイルの読み込みは残しておいてもよいです)。このコードは、<body>
タグ内の最後あたりに追記します。
<script type="module">
// querySelectorAll()の引数をうまく調整して、処理の対象とするカスタム要素を取得します
let targetElements = [].slice.call(document.querySelectorAll('main > div > *'));
// コールバック関数を登録して、IntersectionObserver のオブジェクトを生成します
let myObserver = new IntersectionObserver((entries, observer) => {
// 対象となる画像要素毎に処理を行います
entries.forEach((entry) => {
// この要素が画面に入ってきた場合
if (entry.isIntersecting) {
// この要素を画面に追加する
let myElm = entry.target;
let tagName = myElm.localName; // カスタムタグの名前
// JavaScriptファイルをインポートします
// カスタムタグの名前が、対応するJavaScriptファイル名になっている前提です
import(`./${tagName}.js`)
.then((module) => {
//console.log(module.default);
// インポートしたモジュールが、module にセットされています
// JavaScript ファイルで定義したクラスをカスタムタグ名に紐付けます
customElements.define(tagName, module.default);
})
.catch(err => {
console.log(err);
});
// 表示したカスタムタグは観察対象から外します
myObserver.unobserve(myElm);
}
});
});
targetElements.forEach((elm) => {
// 各要素の観察を開始します
myObserver.observe(elm);
});
</script>
- Intersection Observer API を使って、観察対象となる要素が画面に入ってきそうになったら、JavaScriptファイルをインポートしています。
- なるべくコメントに、何をやっているのか書きました。
3. おわりに
2つの改善点を紹介しました。
2つ目は大変そうに思えるかもしれませんが、これくらいのコード量で遅延読み込みが実現できるのであれば、少し頑張ってみる価値はあると思います。
4. 参考
- 1つのWebページに複数のHTML要素サンプルを記述する – ラボラジアン
- JavaScript modules – JavaScript | MDN
- : The Script element – HTML: HyperText Markup Language | MDN</a></p>
- 4.12.1 The script element | HTML Standard
- Intersection Observer API – Web API | MDN