テキストを画像化してimg要素としてdivに配置し、iPhoneやデスクトップ版のSafariで表示したら、なんだか画像のサイズがおかしい。CSSに指定したheightの寸法よりも大きく見える。インスペクタで要素の高さを確認したところ、問題のimg要素だけheightの計算値がおかしい!こんな現象にハマりました。発生条件が判ったのでメモ。
動作環境
macOS 10.14 (Mojave), Safari 13.1
現象
要素div.tabに固定のheightと上下にpaddingを持たせ、子要素としてimg要素をheight: 100%で埋め込みます(下図)。図で「LOREM IPSUM」はテキストを画像化したものです。デスクトップ版のChromeやFirefoxでは、上側のようにLOREM IPSUM画像がdiv.tabのコンテンツボックスにぴったり収まって表示されますが、Safariで表示すると、ある条件で画像がコンテンツボックスをはみ出してしまいます。
サンプルのコード
下記サンプルでは、テキストを画像化したファイルlabel.pngを2つの方法で親要素div.tab内に表示しています。最初のdiv.tabは、img要素の高さをheight: 100%で指定したもの。2つ目のdiv.tabは、img要素の高さをピクセル値で直接指定したものです。
<divclass="sample"><divclass="tab"><imgsrc="img/label.png"alt=""></div></div><divclass="sample"><divclass="tab px"><imgsrc="img/label.png"alt=""></div></div>.sample{width:300px;margin-bottom:20px;}.tab{height:26px;padding-top:8px;padding-bottom:8px;background-color:lightgoldenrodyellow;}.tabimg{height:100%;width:auto;}.tab.pximg{height:26px;width:auto;}Safariで表示した場合
冒頭に示した現象は次の手順で再現させることができます。下図のアニメーションです。Safariのインスペクタを開いて、.tabセレクタのpadding-top値を0から徐々に大きくしてみましょう。すると……
padding-topが19pxになった時、「LOREM IPSUM」が下側のものより少し大きく表示されるのがわかります。
この時、img要素の高さの計算値は27pxで、親要素div.tabのコンテンツボックスの高さ26pxを超えてしまいました。そのままpadding-top値を増やしていくと、画像もどんどん大きくなっていきます。
しきい値27pxとは、どうやら
padding-top + padding-bottom > height
となる地点のようです。つまり、height: 100%を指定したimg要素に対して、Safariはimg要素の高さを次のように算出しているように見えます。
| padding-top + padding-bottom | img要素のheight計算値 | |
|---|---|---|
| 1 | height以下 | divのcontent boxの高さ |
| 2 | heightより大 | divのpadding boxの高さ |
CSSの仕様を確認
念のため、heightプロパティの値にパーセンテージを指定した時のCSSの仕様を確認しておきましょう。
heightのパーセンテージの基準は?
MDN: https://developer.mozilla.org/ja/docs/Web/CSS/height
<percentage>
包含ブロックの高さのパーセントで高さを定義します。
W3C: 10.5 Content height: the 'height' property
The percentage is calculated with respect to the height of the generated box's containing block.
包含ブロックとは?
MDN: https://developer.mozilla.org/ja/docs/Web/CSS/Containing_block#Identifying_the_containing_block
包含ブロックを識別するプロセスは、要素の position プロパティの値に全面的に依存します。
1. position プロパティが static 又は relative の場合、包含ブロックはブロックコンテナー (略) 又は整形文脈を確立する要素 (略) である直近の祖先要素のコンテンツボックスの辺によって構成されます。
W3C: 10.1 Definition of "containing block"
- For other elements, if the element's position is 'relative' or 'static', the containing block is formed by the content edge of the nearest ancestor box that is a block container or which establishes a formatting context.
今回のサンプルはこれに該当するので、img要素のheightをパーセンテージで指定した場合は、やはりdiv.tabのコンテンツボックス(paddingを含まない)の高さが基準になるはずです。1
Chromeで表示した場合
実際、デスクトップ版のChrome(Blink), Opera(Blink)やFirefox(Gecko)では上記の仕様どおりに表示されます。IE11やEdgeブラウザでも問題なく表示されました。下図は、ChromeのDevToolsを使って同様にdiv.tabのpadding-topの値を増やしていった時のアニメーションです。paddingの値によらず、img要素の高さは26pxのまま変わりません。
WebKitの問題?
上記の現象はSafariのレンダリングエンジンWebKitの問題か?と思って、WebKitのBugzillaを検索したところ、ありました!これです。同じ現象です。
Bug 131486 - Percentage height on replaced elements is computed wildly incorrectly
なんと2014年4月10日にBugzillaに登録されて以来、ステータスNEWのまま放置状態orz
今回、たまたまスマホ用にdivのサイズを「divのpaddig > divの(content-boxの)height」になるまで小さく調整したため、このバグを踏んでしまったのでした。仕方なく、この要素だけimgのheightをpxで指定して回避しました。
position: absoluteにした場合は、paddingを含んだ高さが基準になります。 ↩