レスポンシブtableの実装で学ぶflex hack【脱メディアクエリ】
メディアクエリとの確執
私もWebサイト制作を受託した経験がありますが、メディアクエリとはあまり良い思い出がありません…
flexやgridという強力なレイアウト切り替え手段が生まれたにも関わらず、由来がよくわからない謎の数値によって手動切り替えを行う矛盾というか、まあ単純にデベロッパーツールと睨めっこしながらさまざまな画面幅で表示確認をし、タブレットで表示乱れが起こる恒例行事に辟易していただけなのですが…()
768から999に乗り換えよう
そんな私ですが、flex-grow 9999 hackを知って世界が明るくなりました。
それ以来、flexプロパティをいじくり回して様々なレイアウトを実験するようになり。
流石にメディアクエリと完全におさらばすることは難しそうですが、魔法のような自動レイアウト切り替えが次々と実現できて感動の嵐。たのしい。
というわけで、実務で役に立つかは不明だが、flexの理解には繋がりそうなのでちょっと解説記事を書いてみることに。
メディアクエリを使わないレスポンシブテーブルの実現 ~ToListTable~
モバイルでは縦並びリストに切り替わるレスポンシブテーブルを勝手にToListTableと呼んでいます。
完成形はこちら。PCで閲覧の方はぜひ、EDIT ON CODEPENをクリックした遷移先でエディタとプレビューの境界をドラッグし、プレビュー幅ごとにレイアウトが切り替わることを確認してみてください。
ベースとなるtableとstyle
レスポンシブ対応を何もしていない状態。スマホでみるとアレ
<table>
<tbody>
<tr>
<th>tr - 1</th>
<td>tr - 1 cell - 1</td>
<td>tr - 1 cell - 2</td>
<td>tr - 1 cell - 3</td>
</tr>
<tr>
<th>tr - 2</th>
<td>tr - 2 cell - 1</td>
<td>tr - 2 cell - 2</td>
<td>tr - 2 cell - 3</td>
</tr>
<tr>
<th>tr - 3</th>
<td>tr - 3 cell - 1</td>
<td>tr - 3 cell - 2</td>
<td>tr - 3 cell - 3</td>
</tr>
<tr>
<th>tr - 4</th>
<td>tr - 4 cell - 1</td>
<td>tr - 4 cell - 2</td>
<td>tr - 4 cell - 3</td>
</tr>
<tr>
<th>tr - 5</th>
<td>tr - 5 cell - 1</td>
<td>tr - 5 cell - 2</td>
<td>tr - 5 cell - 3</td>
</tr>
</tbody>
</table>
/** ---- BASE ---- **/
table {
width: 100%;
border-collapse: collapse;
}
table th,
table td {
padding: 1em;
background-color: white;
color: #4d608b;
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 5px 0px,
rgba(0, 0, 0, 0.1) 0px 0px 1px 0px;
}
table th {
background-color: #4d608b;
color: #f5f4f0;
padding-left: 1em;
}
step1. カラム幅を統一
ベースとなるCSSに、次のコードを追記。
/** ---- HACK ---- **/
table tbody tr {
display: flex;
}
table tbody tr td,
table tbody tr th {
/* 各セルが幅いっぱい */
flex-basis: 100%;
}
flex-basis
(各アイテムの初期サイズ)を100%に設定しても、display: flex;
によって一行ごとに横並びが強制されているため、セルたち(tdとth)は仕方なく、行内でみんなが最大になれる幅(100% / カラム数
)を均等に分け合います。
step2. 常に縦並びリスト
/** ---- HACK ---- **/
table tbody tr {
display: flex;
/* 改行を許可 */
flex-wrap: wrap;
}
table tbody tr td,
table tbody tr th {
/* 各セルが幅いっぱい */
flex-basis: 100%;
}
flex-wrap: wrap;
を加えると状況が変わります。
セルたちは幅100%になるために改行することが許されるのです。一行にみちみち収まる必要はありません。
結果、セルは完全に縦並びになります。
step3. メディアクエリを使わずに表示領域幅によって切り替える
/** ---- HACK ---- **/
table tbody tr {
display: flex;
/* 改行を許可 */
flex-wrap: wrap;
}
table tbody tr td,
table tbody tr th {
/* 30em < 100% -> 負の数 -> 無効 */
/* 30em > 100% -> 正の数 */
flex-basis: calc(30em - 100%);
/* 幅を戻す */
flex-grow: 1;
}
flex-basis: 100%;
があると、テーブル表示領域(親要素trの幅)に関わらず常に縦並びリストになってしまいます。
最終的には表示領域幅が小さくなった場合のみ、flex-basis: 100%;
を有効にしたいのです。
ここで使うのは、flex-basisは負の数を指定すると無効になるという性質です。
例えば、30em
を境に切り替わるようにしましょう。
flex-basisにcalc(30em - 100%);
を指定すれば、表示領域幅が30emより大きい場合の計算結果が負の数となり、この指定は無効になります。
しかし、これではflex-basisが有効の場合も、セル幅は100%ではなく30em - 100%
になってしまい、各セルが表示領域幅いっぱいに広がったstep2のような縦並びリストは得られません。
そこで登場するのがflex-basis: 1;
です。
flex-growは、親要素に余白がある場合に、その余白をflexアイテムに追加することで、flexアイテムの大きさを伸長させるプロパティです。これにより、縮こまった各セルを再度引き伸ばします。
flex-growの値は余白の配分比率であり、例えば、
-
flex-grow: 0;
を指定した要素は、余白をもらうことはできません。 -
flex-grow: 2;
を指定した要素は、他の要素がもらった余白の2倍分の余白をもらえます。
ここでは、全てのセルにflex-grow: 1;
を指定することで、全セルが均等に余白をもらうようにします。
(flex-growの値はなんでもよく、全てのセルで共通の値を指定すれば同じ表示が得られます。flex-grow: 2;
でもなんでも構いません。)
step4. thの横にtdが回り込めないようにする
これでうまくいったように思えますが、中途半端な表示領域幅になるとthの横にtdが回り込むという、望ましくない現象が起こります。
次のようにコードを修正すると、この問題を解決し、完成形を得ることができます。
/** ---- HACK ---- **/
table tbody tr {
display: flex;
/* 改行を許可 */
flex-wrap: wrap;
}
table tbody tr td,
table tbody tr th {
/* 30em < 100% -> 負の数 -> 無効 */
/* 30em > 100% -> 正の数 */
flex-basis: calc((30em - 100%) * 999);
/* flex-basisが負の場合に幅を戻す */
flex-grow: 1;
}
30em > 100%の場合に、flex-basisの値をとてつもなく大きくするのです。
flex-basisの値 > trの幅という状況を作れば、あくまでもtrの幅からははみ出すことなく、幅いっぱいまで広がるようになります。
終わりに
NoMediaQueryレスポンシブテーブルは他のパターンも実験済みなので、また似たような記事を書くかもです。
参考文献
Discussion