Closed13

グリッドレイアウトの学習記録

ピン留めされたアイテム
keikei

このスクラップについて

『作って学ぶ HTML + CSS グリッドレイアウト』に基づき、内容を自分の言葉でわかりやすく解説・情報整理しています。
具体的な内容や詳細な説明は原著作物を参照してください。
本スクラップで使用している引用部分には出典を明示しています。

参考書籍

もくじ

複数のスクラップに分類するため

フローレイアウト編
CSS Grid編

keikei

フローレイアウト

フレックスボックスは1次元、CSSグリッドは2次元のレイアウトに適しているという認識は本質をわかりづらくする。
CSSグリッドの立ち位置を考えれば、比較対象はWebのレイアウトの根本であるフローレイアウトとされるべき。

それなのに、グリッドレイアウト=フレックスボックスとCSSグリッドを扱うものという間違った認識をされていることがある。

その原因は、異なる目的を持ったレイアウトシステムとして進化してきた経緯を理解してないため。
要するに、結果だけしか見ていなかったからである。

どう違うのか?

存在理由と目的が以下のように違う。

  • フレックスボックスはフローレイアウトを拡張した存在
  • CSSグリッドはフローレイアウトの問題を解決するための存在

フローレイアウトとは、

  • ブロック、フレックス、インライン、テーブル、ランイン、ルビで構成し、要素ごとの設定と相互関係でレイアウトを制御するもの
    -レイアウト設定を指定しない場合はデフォルトで適用される

「フレックスボックスは1次元、CSSグリッドは2次元のレイアウト」という形で比較すると見えない部分が出てきてしまうのは、それぞれのたどった歴史が違うため。

「グリッドレイアウト=フレックスボックスとCSSグリッド」の認識がもたらす問題

1からレイアウトを作るときに、適切な使い分けができない。

特に、フレックスボックス、CSSグリッド、フローレイアウト、それぞれのコードがどの部分なのかを把握しないまま使うのは、「CSSグリッドを使わなくてもフレックスボックスだけで実装できる」という間違った認識に繋がり、結果グリッドレイアウトを活用できていないことにすら気づいていないという状況になる。

認識を間違えてしまう要因

現代のWebデザインは、フローレイアウトで実現可能なものをベースにして進化してきたものという土台があるにもかかわらず、このような覚え間違いをしてしまうのは、ここまでのHTMLCSSでの蓄積が元になっているため。

そうしたデザインを扱う限り、フローレイアウトのレイアウトシステムに問題を感じることがない。
フレックスボックス(フローレイアウト)を使うほうが効率的に感じてしまい、CSSグリッドの必要性をあまり感じなくなってしまうのは当然と言える。

keikei

CSSグリッドが生まれた理由

結論から言えば、時代変化によってフローレイアウトでは対応しきれないスタイリングに対応するために登場したのがCSSグリッドである。

たとえば、WebアプリのUIのようなレスポンシブ環境でレイアウトを崩さずコントロールしなければならないもの。
また、ヘッドレスCMSからのコンテンツやコンポーネントが抱えることになる子要素、想定しきれない要素のスタイリングなど。

いわゆる、異なる形の箱を組み合わせて構築するレイアウトにフローレイアウトは対応できなかった。

フローレイアウトは、ドキュメントのためのレイアウトシステムとして登場したレイアウトシステムなので、このようなデザインを全く想定していなかったので仕方ない…。

keikei

以下の情報は書籍27ページの説明より引用

フローレイアウトのルール(視覚整形モデル)

種類 説明
ブロックレベル要素 (display: block や table) - 隣り合う要素の上下マージンは相殺(結合、重ね合わせ)される
- 親と子孫の要素の間にパディングなどが入らない場合、上下マージンは相殺される
- 要素の中身が空でパディングなどが入らない場合、自身の上下マージンは相殺される
- 横幅が auto の場合、コンテナブロック(親)の幅に合わせてサイズが決まる
- 高さが auto の場合、中身に合わせてサイズが決まる
- 左右マージンが auto でコンテナブロック内に余剰スペースがある場合、中央に配置される
- 余剰スペースのサイズが auto に割り当てられる
- 上下マージンが auto の場合、0 で処理される
インラインレベル要素 (display: inline や inline-block) - display が「inline」の場合、横幅と高さの指定は適用されない
- display が「inline-block」の場合、横幅と高さの指定が適用される
- 隣り合う要素の間にはそれぞれの左右マージン、ボーダー、パディングが入る
- 左右マージンが auto の場合、0 で処理される
- 上下マージンは相殺されない
- 1 行の高さは子テキストや各要素の line-height や height で決まる
- display が「inline-block」の場合、上下マージン、ボーダー、パディングが1行の高さに影響する
浮動要素 (float: left または right) - 通常フローに従って垂直方向の位置が決まる
- 水平方向はコンテナブロックの左右に揃えた配置になる
- フロートの要素として扱われるが、後続のテキストやインラインレベル要素は回り込む
絶対位置指定された要素 (position: absolute または fixed) - フロー外の要素として、通常フローから独立して扱われる
- position が「static」以外の近辺のコンテナブロックが位置指定の基準(包含ブロック)となる
- 横幅や高さが auto の場合、中身に合わせたサイズになる
keikei

条件を把握して個別に理解すべきルール

参考:MDN Web Docs - margin

書籍28~29ページより情報を引用

隣接要素の上下マージンが相殺される

ルール 説明
上下の隣接要素 垂直方向に隣接するブロックレベル要素の上下マージンが相殺され、最大のマージンだけが適用される。 下記の例1
親と子の要素 親要素とその最初または最後の子要素の間のマージンも相殺される場合がある。 下記の例2
空のブロック要素 高さが0で上下のマージンしかない空のブロック要素も、上下のマージンが相殺される。 下記の例3

例①:上下の隣接要素

+-------------------+              +-------------------+
|                   |              |                   |
|     要素1         |  20px Margin |      要素2        |
|                   |   <--------> |                   |
|                   |              |                   |
+-------------------+              +-------------------+

要素1の下のmargin20pxと要素2の上のmargin30pxが相殺されて、最大値の30pxのみが適用される。

+-------------------+
|                   |
|     要素1         |
|                   |
|                   |
+-------------------+  
<--------> 30px
+-------------------+
|                   |
|      要素2        |
|                   |
+-------------------+

例②:親と子の要素

+--------------------------+
|         親要素           |
|  +-------------------+   |
|  |                   |   |
|  |     子要素        |  30px Margin
|  |                   |   |
|  +-------------------+   |
+--------------------------+

親要素と最初の子要素の間のmargin30pxが相殺され、子要素のmargin30pxのみが適用される。

+--------------------------+
|         親要素           |
|                          |
|                          |
|                          |
|  +-------------------+   |
|  |                   |   |
|  |     子要素        |  30px Margin
|  |                   |   |
|  +-------------------+   |
+--------------------------+

例③:空のブロック要素

+--------------------------+
|         空の要素         |  20px Margin
|                          | <-------->
|                          |
+--------------------------+   
+--------------------------+
|       次の要素           |  30px Margin
|                          | <-------->
|                          |
+--------------------------+

高さ0の空ブロック要素の上下margin(20pxと30px)が相殺されて、最大の30pxのみが適用される。

+--------------------------+
|         空の要素         |
|                          |
|                          |
+--------------------------+   
<--------> 30px
+--------------------------+
|       次の要素           |
|                          |
|                          |
+--------------------------+

簡単に言うと…

隣接する要素の上下marginが相殺されるとき、値が大きい方のマージンが優先されて適用される。
重複するmarginをまとめられて、余計なスペースが生じないようにするためのルール。

keikei

子要素のマージンが親要素の中に納まらない

30px Margin
|
+--------------------------+   <-- 親要素の境界(div)
|                          |
|  +-------------------+   |
|  |                   |   |
|  |       <h1>        |   |
|  |                   |   |
|  +-------------------+   |
|                          |
+--------------------------+

<h1>margin-topが親要素の内側ではなく、外側(子要素)に適用されてしまった。
そのため、親要素<div>の上部に30pxの余白が生まれて、親要素の外側にmarginがはみ出ている状態。

本当はこうしたかった

+--------------------------+   <-- 親要素の境界(div)
|                          |
|  30px Margin             |  <-- 親要素の内側に適用
|  +-------------------+   |
|  |                   |   |
|  |       <h1>        |   |
|  |                   |   |
|  +-------------------+   |
|                          |
+--------------------------+

<h1>margin-topが親要素<div>の内側に適用されている。
そのため、親要素<div>の内側に30pxの余白を確保できている。

簡単に言うと…

親要素の上下にmarginを確保しない場合、親要素の最初の子要素や最後の子要素に設定したmarginが親要素の外側に適用されてしまう。
この相殺のルールは、親要素にpaddingborderがない場合に発生する。

keikei

左右のautoマージンの扱いが要素の種類で変わる

例①:<div>要素を中央に配置する場合

ブロックレベル要素である<div>を親要素の中央に配置する場合、margin-left: auto;margin-rigft: auto;を使用する。

親要素(div)
+----------------------------------+
|                                  |
|     +--------------------+       |
|     |                    |       |
|     |  中央に配置された   |       |
|     |       div          |       |
|     +--------------------+       |
|                                  |
+----------------------------------+

これにより、左右のmarginが等しくなり、要素が中央に配置される。

例②<figure>内の画像<img>を中央に配置する場合

画像を含む<figure>要素を使って画像を中央に配置する場合、<figure>text-align: center;を設定し、<img>の左右marginautoを設定する。

親要素(figure)
+----------------------------------+
|                                  |
|     +--------------------+       |
|     |                    |       |
|     |    中央に配置された   |       |
|     |       画像          |       |
|     +--------------------+       |
|                                  |
+----------------------------------+

親要素の中で、子要素の<img>が左右のmaragin: auto;によって中央に配置されるという仕組み。

簡単に言うと…

要素の種類 autoマージンの挙動
ブロックレベル要素 左右のマージンが等しくなり、中央揃えされる。
インラインレベル要素 autoマージンは適用されず、配置には影響しない。
フレックスアイテム 他のアイテムから離れて配置される。
keikei

フローレイアウトでレイアウト制御をするのは難しい

このレイアウトシステムを自由に制御するには、「フローレイアウトのルール」を理解したうえで、「すべての要素のスタイルを把握すること」と「要素の相互関係でレイアウトを組むこと」の2点を求められる。

つまり、「各要素の設定と相互関係」を考慮する必要があり、難しくさせてしまう要因になっている。

簡単に言えば、一度書けば変更されないという前提でレイアウトする場合であれば問題はないが、現代のWebサイトは動的な処理、UIコンポーネントなどの外部から持ってきたものを組み合わせて構築する状況に対応できないということ。

フレックスボックスとハックの登場と方向性の限界

フローレイアウトで複雑なレイアウトを組むために、「ハック」や「フレックスボックス」を使ったレイアウトが生み出された。
しかし、フローレイアウトの限界は解消できず、進化の方向性として限界を迎えた。

CSSグリッドレイアウト(CSS Grid)の登場

「フレックスボックス」はフローレイアウトというベースシステムを土台にした進化の方向性のひとつだったが、現代のWebサイトに求められることを実現するためには限界があった。

結果、フローレイアウトというベースから「CSS Grid」という異なる考え方を持つ派生の進化系が生み出された。

keikei

リセットCSSの誕生

フローレイアウトを複雑にしているもののひとつに、ブラウザのCSS(UAスタイルシート)がある。

当時のWeb開発者はどの要素にUAスタイルシートが適用され、どのように表示されているかを把握しながらコードを書く必要があった。
UAスタイルシートが原因で思わぬレイアウト崩れが発生することも多々あったとか。

この問題を簡単に、わかりやすく解決したのが「リセットCSS」と呼ばれるライブラリ。

これらはUAスタイルシートで設定された要素の上下marginやリンクの下線を削除したり、フォントサイズを均一化するなどして、UAスタイルシートを気にせずスタイリングできる環境を整えてくれた。

簡単に言うと…

昔は、誰かが書いた絵や枠線の邪魔にならないように書き足していたので、うっかりと被ったり、書きたい場所が埋まっていたみたいなことが起こっていた。
「リセットCSS」が登場してからは、まっさらな白紙に自分が好きなように書けるようになったということ。

しかも、必要に応じて残しておきたい設定はそのまま、自由にアレンジすることもできるようになった。
(ノーマライズCSS)

最強の消しゴムってことっすね。

しかし万能ではない

あくまでもUAスタイルシートを削除・整形してくれるだけ。
フローレイアウトでレイアウトを制御するために必要なことがなくなるわけではないので、注意が必要。

keikei

フローレイアウト制御のために生まれたものたち

CSSフレームワーク

フローレイアウトでドキュメント以外のレイアウトを実現しようとすると「ハック」を使うしかなかった。
しかし、とてもめんどくさかったため、効率よく制御するためにCSSフレームワークが生み出される。

こうしたCSSフレームワークは、グリッドシステムを持っていて、12カラムや16カラムに合わせて要素の配置をコントロールできるようになっていった。

特に固定レイアウトでグリッドシステムはfloatを使った疑似的なものだったが、当時は画期的だったらしい。

余談:2011年には「bootstrap」が誕生した。

フクロウセレクタ

外部から持ってきた未知なものを組み合わせてレイアウトを構成できなかったフローレイアウトの限界を解消するために生み出されたのが「フクロウセレクタ」と呼ばれるセレクタ。

しかし、当時はBEMなどによるCSS設計が全盛期で要素ごとのクラスに対して指示的にスタイルを充てて、カスケードや詳細度を極力排除する考え方が一般的だった。
そのため、広範囲をターゲットにするユニバーサルセレクタ「*」を使うフクロウセレクタは否定的に受け取られていたようだ。

コード例

* + * {
    margin-top: 1.5em;
}

ざっくり説明

フクロウセレクタ登場以前は、フローレイアウトでは要素ごとにスタイルを設定していた。
UAスタイルシートによる制限とフローレイアウトの特殊ルールに考慮しながら書く必要があったので、相当不自由なスタイリング環境だったそうで、コードは冗長さがあったようだ。

フクロウセレクタはこうしたコード冗長さを解消した。
ネスト構造にも対応でき、詳細度も低いので必要な場合は要素ごとの設定で簡単に上書きできる点がよかったらしい。

keikei

Tailwind CSSはフクロウセレクタをベースにしている

組み合わせる要素を問わないフクロウセレクタは、コンポーネントとの相性が良い設定だった。
そのためTailwind CSSのブロックエディタではベースにした設定が活用されている。

たとえば、

<div className="spase-y-8">
    <h1>文章</h1>
    …
    <p>…</p>
</div>

というコードで適用されるスタイルは次のようにコンパイルされる。

.spase-y-8 > :not([hidden]) ~ :not([hidden]) {
    margin-top: 2rem;
}

これは、フクロウセレクタの仕組みをベースにhidden属性を持つ要素以外がターゲットになるように設定している。
hidden属性を持つ要素は画面に表示されなくなるが、そのままではセレクタの処理対象になるためだ。

隣接兄弟結合子「+」が使用されていないのは、この例では、隣接する要素がhidden属性を持っていた場合に設定が適用されなくなるのを防ぐため。
そのため、後続兄弟結合子「~」を使用して、hidden属性を持つもの以外を適用対象にしている。

簡単に言うと…

フクロウセレクタの隣接する要素の間にスペーシングを簡単に追加できるという強みを、Tailwind CSSはコンポーネント間のスペーシングを管理する方法として取り入れている。

参考文献

Space Between
Lobotomized Owl Selector Explanation

keikei

ユーティリティクラスベースのCSSフレームワーク

Tailwind CSS(2017年)やPanda CSS(2023年)といった近年話題のフレームワークはユーティリティクラスをベースにした考え方だ。
これらを使うと、ブラウザの適用するUAスタイルシートが全面的にリセットされて、要素ごとに直背的なスタイルを指定できるようになる。
フローレイアウトの細かなルールを意識しなくても、直感的にわかりやすくスタイリングできるようになったことも、これらフレームワークが好まれる理由の一つと言えるかもしれない。

参考文献

Tailwind CSS Preflight

このスクラップは5ヶ月前にクローズされました