レンダリングツリー構築のプロセスを理解する
お弁当箱でイメージする領域確保
2023年4月3日
著者: 竹洞 陽一郎
はじめに
水は方円の器に随う
CSSは、文書装飾を担い、Webページの表示に直接影響をもたらします。
CSSで考慮すべきことに、CLS(Cumulative Layout Shift)の最小化があります。
今回は、CSL対策にも重要なCSSでの領域確保について解説します。
CLSとは
Cumulative Layout Shift(CLS)は、Webページが読み込まれた後のレイアウトの変更を測定する指標です。
ユーザーが画面を見ているときに突然要素が動くと、使いづらくなることがあります。
これは通常、画像や動画、フォントなどのリソースが読み込まれてレイアウトが変更されるときに発生します。
CLSはその変更を測定し、ユーザー体験の安定性を評価します。
CLSの計算式
CLSは、Layout Shift Scoreの合算となり、それで累積(Cumulative)という単語が付いています。
Layout Shift Scoreは、以下の計算式で求めます。
Layout Shift Score = Impact Fraction * Distance Fraction
- Impact Fraction
-
シフト前とシフト後の視点で見た、画面に占める移動した要素の面積の最大値。
Impact Fractionは単位を持たない比率です。
これはシフト前とシフト後における、画面に占める移動した要素の面積の最大値を画面全体の面積で割ったものになります。
したがって、Impact Fractionは0から1の間の値を取ります。
0は要素が全く移動していないことを示し、1は要素が画面全体を覆って移動したことを示します。 - Distance Fraction
-
移動した要素が画面の縦または横方向に移動した距離を、画面の縦または横方向の長さで割った値。
Distance Fractionもまた単位を持たない比率です。
これは画面の縦または横方向に移動した要素の最大距離を、同じ方向のビューポートの長さで割ったものになります。
したがって、Distance Fractionも0から1の間の値を取ります。
0は要素が全く移動していないことを示し、1は要素が画面の一方の端から他方の端まで完全に移動したことを示します。
CLSの計算例
例えば、以下のようなケースを考えてみましょう。
- iPhone14での表示
- iPhone14のViewportサイズは、横390 x 844ピクセル、画面全体の面積は、390x844=329,160ピクセル²
- ヒーローイメージが上のヘッダ要素が可変であるためにずれてしまう。
- ヒーローイメージは、iPhone14上でのサイズは380 x 235ピクセル、画像の面積は、380x235=89,300ピクセル²
- この要素が、navの中の要素が可変であるために、navの領域が変更されることで、下方向に10ピクセル移動
ここで、Impact FractionとDistance Fractionを計算します。
- Impact Fractionの計算
-
要素が移動する前後で画面に占める面積の最大値を画面全体の面積で割ります。
この場合、要素の面積は380x235=89,300ピクセル²。
画面全体の面積は390x844=329,160ピクセル²。
したがって、Impact Fractionは 89,300 / 329,160 ≒ 0.27 になります。 - Distance Fractionの計算
-
移動した要素が画面の縦方向に移動した距離を、同じ方向のビューポートの長さで割ります。
この場合、要素は10ピクセル移動し、ビューポートの高さは844ピクセルです。
したがって、Distance Fractionは 10 / 844 ≒ 0.01 になります。 - レイアウトシフトスコアの計算
-
レイアウトシフトスコアは Impact FractionとDistance Fractionの積で計算されます。
このケースでは、レイアウトシフトスコア = Impact Fraction * Distance Fraction = 0.27 * 0.01 = 0.0027 になります。
このようにして各要素のレイアウトシフトスコアを計算し、それらを全て合計したものがページ全体のCLSになります。
上述の計算例のように、CLSの値は、端末の画面面積と該当する移動した要素の面積の比で計算されるため、端末の画面サイズに大きく影響されます。
各要素をレスポンシブデザインで表示するにしても、要素の表示サイズを小さくするのは視認性と操作性の都合で限界があるため、小さい画面の端末ほど、CLSの値は大きくなる傾向にあります。
配置方法の原則
このようにCLSを計算して、改善していく際に、基礎知識として押さえておきたいのが、配置方法の原則です。
HTMLには様々な要素があり、そのデフォルトの配置方法は大きく2つに分類されます。
ブロックとインラインです。
ブロックは、新しい行として、垂直方向に並んでいきます。
インラインは、ブロックの中で、流し込まれた水のように、水平方向に並んでいきます。
ブロック
ブロックは、常に新しい行から始まり、その後に続く要素も新しい行から始まります。
つまり、これらの要素は周囲のコンテンツフローに「ブロック」を作ります。
ブロックの幅は、親要素の幅をデフォルトで全て使うため、横幅は容易に変更することができますが、縦幅はその中に含まれるコンテンツによって決まります。
ブロックの例としては、以下のものがあります。
<header><main><footer><article><section><div><p><h1>〜<h6><ol><ul><li>
インライン
一方、インラインは、テキストやその他のインラインと一緒に行内に配置されます。
これらの要素は新しい行から始まらず、前後のテキストや他のインライン要素と一緒に一つの行に流れるように配置されます。
そのため、インラインは基本的には縦幅と横幅を持たず、その大きさはその中に含まれるコンテンツによって決まります。
インラインの例としては、以下のものがあります。
<span><a><img><ruby><em><samp><code><mark><cite><em><dfn>
HTML/CSSを利用したページレイアウトでは、これらの要素がどのように配置・表示されるかを理解することが重要です。
ただし、CSSのdisplayプロパティを用いることで、ブロックレベル要素をインラインのように、またはその逆に扱うことも可能です。
例えば、CSSグリッドは、各アイテムのブロックをインラインのように、横に並べることが可能ですよね。
ブロック要素か、インライン要素か?
「それぞれのタグは、どれがブロック要素、インライン要素か?」というのは、HTMLやCSSの仕様では定められていません。
それらは、各ブラウザのデフォルトCSSで定義されています。
z-indexで見るHTMLのレイヤー
HTMLの領域確保で、もう一つ大事なのが、z-indexです。
縦と横の二次元だけではなく、奥行きの概念を取り入れて、レイヤリングできるようにしたものです。
レイヤリングとは
レイヤリング(Layering)とは、特定の視覚的な要素を他の要素の上に重ねて表示するデザインや開発のテクニックを指します。
ウェブデザインのコンテクストでは、レイヤリングは通常、CSSのz-indexプロパティを使用して制御されます。
z-indexプロパティは要素のスタック順序(つまり、他の要素に対する位置)を制御します。
要素はz軸(前後の深度)に沿って配置され、高いz-index値を持つ要素が低いz-index値を持つ要素の上に表示されます。
たとえば、ドロップダウンメニューやモーダルウィンドウ、ツールチップなどは、通常、他の要素の上に表示されるため、z-indexが高い値に設定されます。
これにより、これらの要素が他の要素に覆い隠されることなくユーザーに表示されます。
しかし、z-indexはpositionプロパティがstatic以外(つまり、relative, absolute, fixed, stickyなど)に設定された要素にのみ適用されます。
これは、z-indexがスタッキングコンテクストという概念と密接に関連しているためです。
スタッキングコンテクストは、その内部で新たなレイヤリングのルールが作られる要素の範囲を定義します。
レイヤリングは視覚的な効果とインタラクションのために重要ですが、適切に管理しなければ、意図しない重ね合わせや視覚的な問題を引き起こす可能性があります。
そのため、z-indexとスタッキングコンテクストの理解と適切な管理は、効果的なレイヤリングのために重要です。
ここでは、話を単純にするために、z-indexを使わない状態での、デフォルトレイヤリングを見てみましょう。
Webブラウザでは、z-indexを指定しない場合でも、いくつかの規則に基づいて暗黙的なレイヤリング(スタッキング)が行われます。
この規則は通常、要素がHTML文書内でどのように記述されているか(つまりDOMの順序)、および特定の要素の種類によって決定されます。
具体的には、次のような規則があります。
- 同じスタッキングコンテキスト内の要素
-
同じスタッキングコンテキスト内で、DOMで後に現れる要素は、前の要素よりも前面に描画されます。
つまり、HTMLファイル内で下に位置する要素ほど前面に表示されます。 - 特定の種類の要素
-
一部の特定の種類の要素は、他の要素よりも常に前面に表示されます。
例えば、position: fixedまたはposition: stickyが適用された要素、または<dialog>要素などがこれに該当します。
ただし、これらの規則はz-indexプロパティが明示的に指定されている場合には上書きされます。
また、新しいスタッキングコンテキストを作成するプロパティ(position: relative/absolute/fixed/stickyとz-index、またはopacity、transform、filterなど)が適用されている要素も、これらの規則に影響を及ぼします。
z-indexの計算処理
z-indexは、実際のところ、レンダリング処理のどこで計算されるでしょうか?
レンダリングプロセスをおさらいしましょう。
- 1. HTML解析
-
ブラウザはHTMLを解析してDOM (Document Object Model) ツリーを作成します。
DOMはページ上の全ての要素と属性を表すツリー構造のデータモデルです。 - 2. CSS解析
-
ブラウザは次にCSSを解析してCSSOM (CSS Object Model) ツリーを作成します。
CSSOMはCSSのルールを表すツリー構造のデータモデルで、どの要素にどのスタイルが適用されるべきかを定義します。 - 3. レンダーツリーの作成
-
DOMとCSSOMは組み合わせられてレンダーツリーを作成します。
レンダーツリーは実際に画面上に表示されるべき要素と、それらの要素のスタイル情報を持つツリー構造のデータモデルです。
非表示の要素(display: none;など)はこのツリーには含まれません。
ここでz-indexの値も考慮されます。 - 4. レイアウト
-
レンダーツリーが作成されると、ブラウザは各要素が画面上のどこに配置されるべきかを計算します。
これをレイアウトまたはリフローと呼びます。 - 5. ペイント
-
最後に、ブラウザはレンダーツリーの各要素を画面に描画します。
これは2つのフェーズに分けられます。
初めに、各要素のアウトライン(色やグラデーションなど)を描画する"ペイント"フェーズがあります。
次に、テクスチャ(画像やテキストなど)を描画する"コンポジット"フェーズがあります。
CSSでボックス要素の領域指定をする
CSSのbox-sizingプロパティは、要素の幅と高さがどのように計算されるかを決定します。
特に、要素のパディングとボーダーがその計算にどのように影響するかを制御します。
box-sizingプロパティには主に2つの値があります。
- content-box (default)
-
これはCSSの初期値です。
widthとheightプロパティは要素のコンテンツボックスだけを指し、パディングとボーダーはその上に追加されます。
したがって、要素の全体的な可視サイズは、width + padding + borderによって決まります。marginはいずれの計算にも含まれません。 - border-box
-
この値が設定されると、widthとheightプロパティは要素のコンテンツボックスとパディングとボーダーを含みます。
その結果、パディングとボーダーの厚さが要素の全体的な大きさを超えることはありません。
widthやheightを指定すると、そのサイズがパディングとボーダーを含む全体のサイズとなります。
ブロック要素の幅の指定
CSSにおいて、widthプロパティは要素の幅を設定するために使われます。
その値としてパーセンテージ(%)とビューポートの幅(vw)を使うことができますが、それぞれが表す意味や振る舞いは異なります。
- %(パーセンテージ)
-
パーセンテージ値を使った場合、その要素の幅は親要素の幅に対する相対値となります。
例えば、ある要素のwidthプロパティを50%に設定した場合、その要素の幅は親要素の幅の50%になります。
親要素の幅が変われば、その子要素の幅もそれに応じて変わります。 - vw(ビューポートの幅)
-
一方、ビューポートの幅(vw)を使った場合、その要素の幅はビューポート(ユーザーが見ているブラウザウィンドウの領域)の幅に対する相対値となります。
1vwはビューポートの幅の1%に相当します。
例えば、ある要素のwidthプロパティを50vwに設定した場合、その要素の幅はビューポートの幅の50%になります。
ビューポートの幅が変われば、その要素の幅もそれに応じて変わります。
bodyの指定
要素の内包関係を考えてみましょう。
全ての要素は、<body>の上に載ります。
ですから、bodyについては、vwを使って指定するのが適切です。
body {
width: 100vw;
}
この指定により、ビューポートのピクセル数が、全ての親となる<body>の幅として指定され、以後、継承されることになります。