↕️

なぜ<img>はインライン要素なのに上下の margin が適用されるのか?

2024/02/26に公開

ある日の出来事

このようなコードをレビューに提出しました。
(説明に必要ないところは抽象化しています。)

<section class="space-y-4">
  <div>
    <img class="hidden lg:block" alt="デスクトップ用画像" />
    <img class="lg:hidden" alt="モバイル用画像" />
  </div>
  <div><!-- 何かのコンテンツ --></div>
  <div><!-- 何かのコンテンツ --></div>
  <div><!-- 何かのコンテンツ --></div>
</section>

画像をモバイルとデスクトップで分ける必要があったので 2 つの<img>が必要でした。
また、親要素にspace-y-4が指定されているため不要な余白が入らないように<img><div>で囲いました。

レビューに提出すると、この箇所で以下コメントをいただきました。

img はデフォルトで inline なので、lg:block とデフォルトので差がありそう。それで div で囲ったのかもしれないけど

このコメントを読み「確かに!」と思い、修正してみましたがうまくいきませんでした。

image

なぜか。上下の margin が適用されています。
右下の Devtools の Computed の displayinline なのに、

Code Pen でも再現しました。

margin-top が適用されています。

原因

最初はフクロウセレクタの結合子とかの影響かなと考えていましたが、
気になり調べた結果 stack overflow にこんな質問がありました。

https://stackoverflow.com/questions/61957967/why-does-the-img-tag-accept-the-margin-top-property

簡単に内容を翻訳すると

質問

I am wondering why img tag accept margin top property? isn't this an inline tag? and inline tags does't accept top and bottom margins?
なぜ img タグは margin top プロパティを受け入れるのでしょうか?これはインラインタグではないのですか?

回答

It's because img is an inline replaced element and it does accept margin-top. It behaves differently than inline non-replaced element (like span for example).
img はインライン置換要素であり、margin-top を受け入れるからです。これは、インラインの非置換要素(たとえば span など)とは異なる動作をします。

どうやら、置換要素が原因ということはわかりました。

ただ、なぜ置換要素だと上下の margin が適用されるかまではわからなかったのでさらに調べてみました。

ブロック要素とインライン要素の違い

まずは、ブロック要素とインライン要素の違いについてです。

ブロック要素

デフォルトで幅が親要素を満たし(width: 100%)、新しい行から始まり、幅と高さの指定が可能です。
さらに上下の margin を適用できます。

インライン要素

コンテンツの流れに沿って配置され、幅と高さは内容によって自動的に決定されます。(width: auto)
インライン要素では上下の margin は適用されません。

置換要素とは

置換要素とは、その内容が外部によって提供される要素です。
HTML タグで言うと<img>,<iframe>,<video>など外部のリソースを表示する際に用いられるものです。

これらの要素は、内部コンテンツに基づいて固有の幅と高さを持つことができます。
また、置換要素は、ブロックレベル要素とインライン要素の間にある特別なカテゴリと見なすことができ、特定の条件下で上下の margin を適用できます。

イメージで言うとブロック要素とインライン要素の中間のような要素。
ただ、display は、あくまで inline です。

🥛👶「その特徴はinline-blockやないかい」

そうです、置換要素は限りなく inline-block のような振る舞いをします。
でも、置換要素は inline なのです。

なぜこんなややこしいことになってしまったのか。
CSS の歴史を振り返れば理解できます。

CSS の歴史

第一章 生命の誕生

生命の始まりは、まだ太陽の光が地上に届かない頃、間欠泉の地下で始まりました。
間欠泉の地下では、生命の材料となる様々な分子「生命構成単位」が出来上がっていきました。

間欠泉内部では、100°C になると地上への噴出により水が入れ替わります。
これにより水は 100°C を超えることはなく、作られた分子は守られました。

また、地下で還元、地上で酸化の場を提供したことも分子の合成には不可欠でした。
このような乾湿サイクルの場は、生命構成単位を合成する大切な場でした。

脂肪酸が集まり生命を包む膜となり、ウェットとドライな状態が繰り返されることで重合反応が進み触媒活性を持つタンパク質原始物質が作られました。
そして、これらの分子が循環し、混じり合うことによって、さらに複雑な分子へと発展しました。

生命を包む分子「原始 RNA」がさらに「酵素様原始物質」と混じり合い、自己複製機能を持つ「リボザイム」に進化します。
リボザイム
リボザイム

こうして分子は、生命の「配列」を複製する力を身につけました。
そして、これらが脂質の膜に取り込まれ「原始生命体」が生まれました。

第二章 初期の CSS

初期の CSS では、display プロパティの値は block | inline | list-item | none しかありませんでした。
現代では考えられませんが、これらの値だけで画面ができていたのです。

この時から<img>も置換要素として存在しました。
置換要素とは、先述した通りに

イメージで言うとブロック要素とインライン要素の中間のような要素。

です。

固有の幅と高さを持つことができ、上下の margin も適用できます。
しかし、この時代にはinline-blockは存在しないので

🥛👶「その特徴はinline-blockやないかい」

と言うツッコミは、的を得ていません。

第三章 CSS2 の登場

CSS2 でやっと inline-block が登場しました。

これは筆者の想像ですが、置換要素は inline なのに block のような振る舞いをしてわかりづらいから、ブロック要素とインライン要素の中間となる inline-block 作ったら便利じゃね?となったのではないかと考察しています。

しかし、inline-block が登場したものの<img>などの置換要素は相変わらずdisplya: inlineのままでした。
この状況は現代でも続いています。

これは、後方互換性を担保するためです。
現にドキュメント内で

'インラインブロック'、通常のフローにおける置換要素
インライン置換要素と全く同じ。

と記載されています。

結論(まとめ)

  • <img>に上下の margin が適用されるのはインライン置換要素だから
  • inline-block は初期の CSS では存在せず inline なのに block のような振る舞いをしていた。
  • CSS2 でinline-blockが登場して、インライン置換要素の立場がややこしくなった。

余談

ちなみに Tailwind CSS ではインライン置換要素はデフォルトで display: block になっているようです。
image

おわりに

今回は、なぜ<img>に上下の margin が適用されるのか調べてまとめてみました。
過去の CSS のドキュメントを読むなんてことはなかったので勉強になりました。

可能な限り裏どりはしましたが、何か間違っている箇所があれば指摘していただけるとありがたいです。

参考文献

https://developer.mozilla.org/ja/docs/Web/CSS/display

https://www.w3.org/TR/REC-CSS1/#display

https://www.w3.org/TR/CSS2/visuren.html

https://www.youtube.com/watch?v=GPdLEKzHd1g

Discussion