display:table-cell;を安易に使うべきでない理由いろいろ


display:table-cell;は特性を理解してから使おう

カンタンに横並びや縦横中央揃えができるという理由から「使わな損やで?」みたいに取り上げられがちなdisplay:table-cell;ですが、それ以上に仕様と特性をちゃんと理解していないと、結構危険なCSSプロパティです。

初心者なら「floatって挙動がよくわからないし、clearfixもどう使ったら良いのかわからん。」と理由で、上級者なら「IE8以上から上下中央揃えを実現できるなんて最高だぜヒャッホゥ!」といった理由から選択しがちですが、使う場合は以下の様なデメリットがあることを必ず踏まえておいてください。

レスポンシブレイアウトでの自由さがない

コーディングの際にPCで5列、スマホで1列になるグリットレイアウトのデザインがあったとします。
そしてHTMLはなるべく分かりやすく、メンテナンスも簡単に済むように以下の形式で組みたい。

<section>
<h2>table-cell</h2>
<ul class="gridTable05">
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキスト</a></li>
</ul>
</section>

<section>
<h2>float</h2>
<ul class="gridList05">
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</a></li>
<li><a href="#">テキストテキストテキストテキストテキストテキスト</a></li>
</ul>
</section>

上記のHTMLにCSSを当てて、それぞれ再現するとこのようになります。

上のリストがdisplay:table-cell;で再現したもの(<ul>タグにdisplay:table;を指定。<li>タグにdisplay:table-cell;。スマホ時はdisplay:block;で1列に。)、
下のリストはfloatで再現したもので、どちらも要件を満たしています。

が、デザイン修正や仕様変更で「うーん、やっぱりタブレットは対応しない要件だったけど。ここだけは3列になるようにしたいな。」「やっぱりスマホは2列でいこうか。」(修正対応あるある)なんて言われたときにdisplay:table-cell;だと...

チーン/(^o^)\

当たり前ですが、display:table-cell;で実装したほうはこのような要件に全く対応できません。
display:table-cell;は行を改める場合にdisplay:table-row;(もしくはdisplay:table;で仕切りなおすか)が必須になるので、デバイス対応で必要となるレイアウトの柔軟性が欠けています。

もちろんJSで<ul>タグ(display:table;を指定)をブレイクポイントで動的に区切ってやれば実装可能ですが、モジュールごとにレスポンシブの仕様が異なるケースが大半なので、それをいちいち対応するのは現実的じゃないし、そのためだけにJSを入れること自体がナンセンスです。
また、floatで組んだときのようにCSSだけでグリッドの並び順を逆にすることも不可能です。
基本的にレスポンシブ対応では、display:table-cell;じゃないと実現できない要素が存在する場合のみの使用にとどめたほうがいいでしょう。

レスポンシブ対応のグリッドレイアウトを実現する代替案

レガシーIEでも対応したい

  • floatと高さを揃えるJS(fixHeight.js)がベスト
  • 次点でdisplay:inline-block;と高さを揃えるJSだが、CSSだけでグリッドの並び順を逆にすることが不可能。
  • 高さを揃えるJSを使わずにmin-heightで何とかしようとするのは、テキスト量が増減したときに困る上に、モジュールごとに固有のmin-heightを指定しなければならない、文字サイズ拡大に対応できないなどの弊害があるため現実的ではない。(ちなみに高さを揃えるJSだったら何でも良いわけではなく、文字サイズ拡大やウィンドウのリサイズに対応していて、行ごとに高さを揃える個数を指定しないで済むfixHeight.jsがオススメ)

IE10からの対応でOK

  • display:flexbox;(ベンダープレフィクス必須、IE11以降はdisplay:flex;
  • そして、毎度のことながらIE様のバグには注意

Normalizing Cross-browser Flexbox Bugs — Philip Walton
http://philipwalton.com/articles/normalizing-cross-browser-flexbox-bugs/

  • 追加でFlexboxのpolyfillがあったので紹介します

IE8・9にもFlexboxを対応させる、flexibility.jsがとっても便利! | Webクリエイターボックス
http://www.webcreatorbox.com/tech/ie8-flexbox/

min-height,max-heightが効かない

これはCSS 2.1の仕様にも書かれていることですが、

In CSS 2.1, the effect of 'min-height' and 'max-height' on tables, inline tables, table cells, table rows, and row groups is undefined.

上記の要素(table, inline-table, table-cell, table-row, table-row-group)において、min-height,max-heightは定義されないというのが正しい仕様となっているようです。

Chrome,Firefox,Opera,Safariでは仕様通りで、display:table-cell;min-heightは効きません。
IEとEdgeは効きます。EdgeHTML EngineってWebkitと同様の仕様になってんじゃねーのかよ爆発しろ。/(^o^)\

さてさて、ここで問題になるのがmin-heightです。
min-heightは使用率が高めなので、使えないのは困りどころです。

.displayTableCellSelector:before {
  content: "";
  display: inline-block;
  vertical-align: middle;
  height: 100%;
  min-height: XXXpx;/* 任意のmin-heightを入力 */
}

上記のように疑似要素を使うことで、display:table-cell;に対してなんちゃってmin-heightが指定できるようになりますが、これを使ってしまうと肝心のvartical-alignが利かなくなってしまうので、display:table-cell;を使うことでの1番のメリットが消えます。min-heightを効かせようとすると縦横中央揃えは実現できなくなるということです。
(打消し部分の件は勘違いだったようです。ただし、上記のコードを使うと結局display:table-cell;を使わないで縦横中央揃えする方法とほぼコードが変わらなくなってしまうため、あんまり意味がないです。この辺は文章だけではわかりにくいと思いますので、codepenにコードを書きました。)

display:table-cell;にmin-heightするとどうなるのか
http://codepen.io/sawadays0118/pen/LGjaqG

こちらもdisplay:inline-block;と擬似要素を使うことで、min-heightを効かせつつIE8以上から縦横中央揃えすることが可能なので、やはり使用はオススメできません。(詳しくは、codepenの最後のコードを参照)

自動レイアウトアルゴリズムの影響

word-breakとword-wrapはややこしい
https://w3g.jp/blog/confusing_word-break_word-wrap

word-wrap:break-word;が効かない場合

長い英単語や長いURLの折り返しを制御するにあたって、望ましい振る舞いをしてくれるword-wrap:break-word;(overflow-wrap:break-word;)ですが、効いていないような振る舞いになるスタイル状況下があります。shrink-to-fit width(内容にぴったりと合うように縮んだ幅)になっている場合と自動レイアウトアルゴリズム(table-layout:auto;)を使用しているテーブルセル(display:table-cell;)の場合です。

shrink-to-fit widthとは、display:inline-block;, float:left;, float:right;, position:absolute;などのプロパティと値が指定された要素で幅が指定されていない(width:auto;)場合に、ブラウザが勝手に内容にぴったりと合うように適当な領域を確保してくれている状況を言います。

shrink-to-fit widthと自動レイアウトアルゴリズムのテーブルセルの場合、その内容の幅によって自身の幅が決められるため、word-wrap:break-word;による折り返さないと行ボックスの幅からあふれてしまうという計算そのものができないことから結果的にword-wrap:break-word;が効いていないような振る舞いになります。

上記のサイトからの引用ですが、こういう罠もあります。
この自動レイアウトアルゴリズムの影響はword-wrap:break-word;以外にもあるので、やはり多様するのは避けたほうがよさそうです。

Firefoxは2014年6月までtable関連でpositionが使えたもんじゃなかった

上記のスライドを見て頂ければわかりますが、Firefox 30まではtable関連にpositionが対応していませんでした。

そして現状はというと、逆にFirefoxだけtable関連の対応度が上回り、
display:table-row;(タグだと<tr>)などもpositionの基点にできるようになっています。
要は今でも仕様がブラウザ間で統一されていない状況だということです。
これも安易に使うべきでない理由のひとつです。

あとがき

ここまで書くと「一気に使いたくなくなった...」って方が増えたと思いますが、特性をちゃんと理解して使う限りは問題ないと思います。
ただし著者は極力使わないようにするのを勧めます。上記のことは全て他の方法で再現できるうえ、仕様の違いについてはコーディングに詳しい方でも知らなかったり、一般的にあまり周知されていない気がするからです。

要点としては、

  • レスポンシブ対応でレイアウト調整が必要な箇所では使わない。
  • 上記をクリアしていて、IE8/IE9以上対応で、なおかつJSを使わずに高さを揃えたい場合。
  • classに変更を加えることなく、列数を増減させる必要がある場合。(一応、floatでもやれないことないですが、結構指定がめんどくさいです。この点table-layout: fixed;の等分割で列数を増やす場合は自動計算なのでラク。)
  • min-height,max-heightを必要とする箇所では使わない。
  • 自動レイアウトアルゴリズムに注意する。
  • positionを指定する際は、仕様がブラウザ間で統一されていないことを念頭にいれておく。(特に旧バージョンのFirefoxに対応する場合は要注意)

上記のようになります。
これらのポイントを抑えて使用する分にはたぶん問題ないはず。(`・ω・´)