横並びなら flex でしょ、となった今だからこそ inline と inline-block を掘り下げる
横並びデザインで inline を用いる
このようなデザインを、みなさんならどう実装しますか。
前提として、アイコンは擬似要素ではなくひとつの div
で、とテキストとは別要素とします。
<div class="container">
<div class="icon">春</div>
<p class="text">春はあけぼの。</p>
</div>
「横並び === display: flex;
だ!」と、脊髄反射するエンジニアは多いのではないでしょうか。かくいう私もです。
アイコンとテキストを包括するコンテナに
.container {
display: flex;
align-items: center;
}
でおしまいです。ありがとうございました。
~ 𝓯𝓲𝓷 ~
しかしこれだと早々に記事が終わってしまうので、もうひとつ題を提示します。
このようなデザインだったらどうでしょうか。そもそもデザイン自体どうなん、といった話はいったん置いておきます。
横並びだ!ということで flex
を使いたくなります。しかし、flex
で実装してしまうとアイコンとテキストの領域がばっさり 2 つに分かれてしまいます。
他には、それぞれをインラインレベルの要素にすることで、横並びを実現する方法がありそうです。このデモでは、横並びの方法をセレクトボックスで変更できるようにしています。
試してみると、このような結果になりました。(アイコンは一律で inline-flex
として扱います)
コンテナで flex | テキストを inline-block に | テキストを inline に |
---|---|---|
これを見ると、テキストは inline
として扱うのが良さそうですね。
では、なぜ同じ inline
という名前を持ちながら、 inline
と inline-block
の結果は異なってしまうのでしょうか。
inline と inline-block の違い
この表は、MDN の CSS display の複数キーワード構文の使用 から抜粋したものです。
単一の値(1 値構文) | 複数の値(2 値構文) |
---|---|
inline | inline flow |
inline-block | inline flow-root |
複数キーワード構文とはなんぞやを超ざっくりレベルで解説すると、inline
は inline flow
を 1 つの値で表現したもので、この 2 つの値の結果は同じです。
2 値構文は、前から順に 外部に対しての表示型 と 内部に対しての表示型 を示します。つまり、inline
は外向きに inline
であり、内向きに flow
で表示する、ということになります。
(より詳細な仕様は、MDN のリンクをご覧ください)
inline
が外向きに inline
であるのは非常に分かりやすいと思います。「とりあえず inline
をつけておけば横に並ぶ」など、その挙動はエンジニアの遺伝子に刻まれているはずです。
しかし、 flow
や flow-root
とは一体何なのでしょうか。
ここで、CSS WG の flow と flow-root の仕様 を参考にしてみます。
とてもざっくりですが、両者にはこのような特徴がありそうです。
flow | flow-root |
---|---|
ブロック、またはインラインの整形コンテキストに属している場合、インラインボックスを生成する。 それ以外の場合は、ブロックコンテナボックスを生成する。 |
ブロックコンテナボックスを新たに生成する。 新たにブロック整形コンテキストが生成される。 |
見慣れない概念に頭がパンク寸前です。inline
と inline-block
をより深く学ぶためにはこれらの用語をしっかり理解する必要がありそうですが、なかなかに複雑な内容なので、ここでは取り急ぎ整形コンテキストとインラインボックスの 2 つを掘り下げてみます。
整形コンテキスト
ブロック整形コンテキスト
MDN の 整形コンテキストの紹介 を参考にします。
丸々抜粋しますが、
ブロックレイアウト規則を使用する文書の中で一番外側の要素は、最初の、あるいは初期ブロック整形コンテキストを確立します。これは、 <html> 要素のブロック内のすべての要素が、ブロックおよびインラインレイアウトの規則に従って、通常のフローに従ってレイアウトされることを意味します。ブロック整形コンテキスト (BFC) に参加する要素は、 CSS ボックスモデルによって概説された規則を使用します。これは、要素のマージン、境界、パディングが同じコンテキスト内の他のブロックとどのように相互作用するかを定義します。
に全てが集約されています。一番分かりやすいのは margin
の相殺です。
要素を縦積みにしたとき、要素が持つ上下 margin
同士が相殺し合ってしまい、意図しないレイアウトになった経験は誰しもあるのではないでしょうか。
これは要素が同一のブロック整形コンテキストに属しているために起こる、margin
の仕様です。では逆に、同一のブロック整形コンテキストに属していない場合はどうなるのでしょうか。
新しい BFC は以下のような場面で生成されます。
display: flow-root または display: flow-root list-item の要素
で試してみます。
特に何もせず要素を縦積みにしたものと、要素に display: flow-root;
を指定したものを用意しました。
flow-root
で包んだ要素は margin
が親に閉じ込められ、それぞれの margin
が維持される結果となっています。
要素の中で新たなコンテキストを生成するという挙動は、以前執筆した isolation: isolate;
の 重ね合わせコンテキストの生成 になぞらえると、しっくりくるかもしれません。
インライン整形コンテキスト
MDN を見ると、いわゆる私たちが見知ったインラインの挙動のコンテキストだと解釈できそうです。
インラインボックス
CSS WG によると、インラインボックスの内容は、インラインボックス自体と同じインライン整形コンテキストに参加するようです。
つまり、要素の内容物(テキストなど)は親と同じラインで横並びで配置されます。
両者の違いまとめ
上記を踏まえて概念レベルでざっくりまとめてみると、
構文 | 挙動 | 冒頭の例で表すと |
---|---|---|
inline | 内容物も親にならったインラインのコンテキストに参加する | ・テキストは親のインラインのコンテキストに含まれる ・テキストはインラインに沿ってそのまま折り返す |
inline-block | それ自身は横並びになるが、内容物は新しいブロック整形コンテキストに収まる | ・テキストは親のインラインのコンテキストに収まらない ・アイコンと同じインラインで折り返しをしない ・折り返しが発生するタイミングで段落ちする |
のようになります。横並びのデモで inline
と inline-block
の結果が異なった理由が分かりました!
おまけ: grid を使わない 2 カラムレイアウトの実現方法 (inline-block)
せっかく inline-block
についても触れたので、ここで inline-block
の使いどころも紹介しようと思います。
HTML 構造や運用の制約などで、grid
を使わずに 2 カラムレイアウトを行いたいことがあります。
その際は、親要素に column-count: 2;
を、子要素に display: inline-block;
を設定することで実現ができます。
inline-block
を指定するのはカラムをまたいだ折り返しを防ぐためです。( break-inside: avoid;
でも OK なのですが、他要素との margin
管理をしやすくするために inline-block
にしています)
デモでは、子要素を display: block;
にするボタンを設置しています。これを押すことで、崩れの様子を再現することができます。
実装当初は「block
の方が折り返さなそうなのになぜ?」と思っていましたが、 flow
や flow-root
の概念を知ることで、なるほどなぁとなったテクニックです。
おわりに
「なんかよく分からんけど inline-block
なら崩れないらしい」「 inline
にしておけばいいらしい」と結果ありきで実装することが多い値ですが、掘り下げると挙動にはちゃんと根拠があることが分かりました。
flow-root
や整形コンテキストは調べ始めると周辺知識も必要となり、かなり複雑な仕様だなと感じましたが、それらをしっかりと理解することで、地に足がついたレイアウト実装ができるようになるなと思います。
私ですか?私はまだこれらの深淵の端っこに立ったばかりです……。 << つづく >>
Discussion