CSS Gridを触って覚える
まず、これを見てください
下の2つは、4等分した列です。上は25%を4本、下は1frを4本。 すきま(gap)を増やしてみてください。上だけが右へはみ出します。
すきまは24pxです。パーセントのほうは72pxはみ出しています。
grid-template-columns: 25% 25% 25% 25%パーセントで4等分。すきまを増やすと、そのぶんだけ右へはみ出します。grid-template-columns: repeat(4, 1fr)fr で4等分。すきまをいくら増やしても、4等分の内側に収まります。なぜ片方だけ溢れるのか。答えは「fr は割合ではなく、余りの分け前だから」です。 この下の道具で組み立てながら、その意味を確かめてください。
親に書くもの
いま書いているのはgrid-template-columns: 1fr 1fr 1frです。
ここに書いた数より子が多いと、行はブラウザが足します(足された行の高さは中身のぶんになります)。
すきまは「中身の入らない、幅の決まった列」として先に取られます。列が3本なら、すきまは2か所ぶん=24pxです。
子に書くもの
grid-column: span 2と書くと、その子はよこに2マス分ぶち抜きます。 ぶち抜いたぶん、あとの子が入るマスがずれます。
どの子が、どのマスに入ったか
ブラウザが順に詰めた結果です。 線の番号は1から数えます(マスの数ではなく、マスとマスの境目の線の番号です)。
| 子 | 入ったマス | 線の番号 | 書いた行 |
|---|---|---|---|
| 1つめ | 1行目・1列目 | よこ 1〜2 / たて 1〜2 | (書かない) |
| 2つめ | 1行目・2列目 | よこ 2〜3 / たて 1〜2 | (書かない) |
| 3つめ | 1行目・3列目 | よこ 3〜4 / たて 1〜2 | (書かない) |
| 4つめ | 2行目・1列目 | よこ 1〜2 / たて 2〜3 | (書かない) |
| 5つめ | 2行目・2列目 | よこ 2〜3 / たて 2〜3 | (書かない) |
書いた列は3本・行は2本で、 実際に使われたのは3列 × 2行です。
中身の幅が600pxだったとすると
- すきまの合計 ── 24px(3本の列なら2か所)
- 1fr の大きさ ── 192px
autoは「中身しだい」なので、中身を見ないと決まりません。分からないものに数を出さないため、 そのときは計算しないで、その旨を書いています。
auto-fit と auto-fill を並べる
どちらもrepeat(auto-fit / auto-fill, minmax(80px, 1fr))で、 中に入れた子は2つだけです。 入る列の数より子が少ないときに、空いた列をどうするかが違います。
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr))空いた列が潰れるので、2つの子が場所を分け合って太くなります。grid-template-columns: repeat(auto-fill, minmax(80px, 1fr))空いた列が残るので、2つの子は細いまま、右に空きができます。auto-fitは空の列を幅0まで潰し、その両側のすきまも消します。 だから残った子が余りを分け直して太くなります。auto-fillは空のまま列を残すので、子は細いままで右に空きができます。 子が列の数だけ揃っていれば、この2つに違いは出ません。
コード
そのまま貼れる形です。上の見本は、この文字列をそのまま当てて描いています。
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 72px 72px;
gap: 12px;
justify-items: stretch;
align-items: stretch;
grid-auto-flow: row;
}HTML のほうも出します。どの div が何番かが分からないと貼れません。
<div class="grid"> <div class="item1">1</div> <div class="item2">2</div> <div class="item3">3</div> <div class="item4">4</div> <div class="item5">5</div> </div>
長い行は、この枠の中だけ横に動かせます(キーボードでも動きます)。
よくある質問
1fr は何パーセントですか?
パーセントには直せません。1fr は「余った場所を1つ分もらう」という意味で、割合ではなく分け前だからです。仕様は fr を「グリッドの中に余っている場所の一部分」と説明し、1fr を「余っている場所の100%」と定めています。ここで言う「余っている場所」は、長さの決まっている列とすきまを先に引いたあとの残りです。だから 200px 1fr 1fr と書くと、まず200pxが取られ、すきまが取られ、その残りを2本で半分ずつ分けます。全体に対する割合ではないので、パーセントに置きかえることはできません。
すきま(gap)があるのに、fr だと溢れないのはなぜですか?
仕様が、すきまを「中身の入らない、幅の決まった列」として数えているからです。つまり列と列のあいだのすきまは、幅の決まった列と同じ扱いで先に取られます。fr が分けるのはそのあとの残りなので、すきまをいくら増やしても合計は親の幅を超えません。パーセントはそうなりません。パーセントは「親の中身の幅」に対する割合で、すきまを引く前の幅を見ているからです。25%を4本並べれば合計はちょうど親の幅になり、そこにすきまが足されるので、すきま3か所ぶんがそのままはみ出します。
repeat(auto-fit, minmax(200px, 1fr)) は何列になりますか?
中身の幅から計算できます。仕様は「グリッドが親の箱からはみ出さない、いちばん大きい整数。ただしすきまも数に入れて」と定めているので、(中身の幅 + すきま)÷(最小の幅 + すきま)の小数を切り捨てた数になります。中身の幅が900px、最小が200px、すきまが16pxなら、916 ÷ 216 = 4.24… で4列です。1本も入らないほど狭いときでも0列にはならず、1列になると決められています。メディアクエリは1行も要りません。
auto-fit と auto-fill はどう違いますか?
中身が足りないときにだけ違いが出ます。どちらも同じ数の列を作りますが、auto-fit は子が1つも入らなかった列を幅0まで潰し、その両側のすきまも一緒に消します。残った子はあらためて余りを分けるので、太くなります。auto-fill は空の列をそのまま残すので、子は細いままで、右側に空きができます。子が列の数だけ揃っている場合、2つはまったく同じ結果になります。3つ並べたカードを画面いっぱいに広げたいなら auto-fit、決まった幅のまま左から並べたいなら auto-fill です。
Grid と Flexbox は、どちらを使えばいいですか?
たてとよこの両方をそろえたいなら Grid、1本の並びだけなら Flexbox です。仕様がその通りに書いていて、「1つの軸を向いている Flexbox と違い、Grid は2次元の配置に向けて作られている。たてとよこの両方でそろえたい場合である」とあります。カードを何列かに並べて、行の高さもそろえたい、というのは2次元なので Grid の仕事です。ボタンを横一列に並べて間隔を決める、というのは1次元なので Flexbox のほうが素直に書けます。片方が新しくて片方が古い、という関係ではありません。
grid-auto-flow: dense を使うと、読み上げの順番も変わりますか?
変わりません。変わるのは目で見たときの並びだけです。仕様は「grid-auto-flow の値や置く場所の指定は、音声などの目で見ない媒体での順番を変えない」と書いたうえで、「作者はこれらを見た目の並べ替えにだけ使うこと。意味の順番をこれで作ったスタイルシートは仕様違反である」と明記しています。読み上げソフトが読む順番も、タブキーで移動する順番も、HTMLに書いた順のままです。順番そのものに意味があるなら、HTMLの並びを直してください。
grid-column: span 4 と書いたのに、列が5本になりました
書いた列の数より大きくぶち抜くと、足りないぶんの列が足されるからです。仕様が「場所を書いていない子の中でいちばん大きいぶち抜きが、いまの列の数より大きければ、それに合うように列を足す」と定めています。3列と書いてあるところに span 4 の子を置けば、列は4本になります。これが厄介なのは repeat(auto-fit, …) と組み合わせたときで、列の数はその場の画面の幅で決まるため、狭い画面では書いたぶち抜きのほうが大きくなり、列が勝手に増えて横にはみ出します。このページで repeat を選ぶとぶち抜きの欄が消えるのは、そのためです。
行と列を組むと、その場でマス目が変わって、CSSが出ます。 いちばん大事な「1fr は余りの分け前」を、パーセントで組んだものと並べて確かめられます。
fr は「割合」ではなく「余りの分け前」です
1frを「だいたい何パーセント」と思って使っている人は多いのですが、この2つは別のものです。 仕様はfrを「グリッドの中に余っている場所の、一部分」と説明し、1frを「余っている場所の100%」と定めています。
大事なのは「余っている場所」がいつ決まるかです。 仕様の順番はこうです。まず長さの決まっている列をぜんぶ配り、そのあとで残りを fr が分ける。だからgrid-template-columns: 200px 1fr 1frと書くと、 200pxが先に取られ、残った場所だけが2本の1frで半分ずつになります。
ここまでは「パーセントでも似たことはできる」と思うかもしれません。違いが出るのは、すきまを入れたときです。
すきまは「中身の入らない列」として、先に取られます
これがこのページで一番言いたいところです。仕様はgapのことを「中身の入らない、幅の決まった、もう1本の列として扱う」と書いています。すきまは「あとで足すもの」ではなく、「先に取られる列」なのです。
列が4本なら、あいだは3か所。すきまが24pxなら72pxが先に取られ、そのあとの残りを1frが分けます。 だからすきまをいくら増やしても、合計が親の幅を超えることがありません。
パーセントはここが逆になります。仕様は「パーセントは、親の箱の中身の幅に対しての割合」と定めていて、すきまを引く前の幅を見ています。25%を4本並べれば、列だけでちょうど親の幅ぴったり。 そこへすきまが足されるので、72px(24px × 3か所)がまるごとはみ出します。
つまり「パーセントで組んで、あとから gap を足した」形は、必ず溢れます。 溢れないように見えているとしたら、パーセントの合計を95%くらいに減らしているか、box-sizingやoverflowで見えなくしているかのどちらかです。fr は、その計算を人間がやらなくていいように作られた単位だと思ってください。
repeat(auto-fit, minmax()) は、メディアクエリを1行も書きません
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))。 この1行だけで、画面の幅に応じて列の数が変わります。 画面の幅を測る指定も、切り替える幅を決める指定も書きません。
何列になるかは計算で出せます。仕様が「親の箱からはみ出さない、いちばん大きい整数。ただし、すきまも数に入れて」と決めているので、答えは(中身の幅 + すきま)÷(いちばん狭い幅 + すきま)の切り捨てです。
中身の幅が900px、いちばん狭い幅が200px、すきまが16pxなら、916 ÷ 216 = 4.24… で4列。 幅が390pxのスマホなら1列です。「すきまも数に入れて」がここでも効いています ── すきまを忘れて計算すると、1列多く数えてしまいます。
なお、1本も入らないほど狭いときでも0列にはなりません。 仕様が「いちばん大きい正の整数」と書いているので、下限は1列です。 幅200pxの列が幅100pxの画面に入るはずはありませんが、列を消してしまうより、はみ出させて見せるほうがましという判断です。
auto-fit と auto-fill の違いは、中身が足りないときだけ出ます
この2つは並べて見ないと違いが分かりません。 上の道具の「auto-fit と auto-fill を並べる」のところに、子を2つだけ入れたものを2つ置いてあります。同じ幅なのに、太さが違います。
仕様はこう決めています。auto-fill が本来のふるまいで、auto-fit は「子を置き終わったあと、空っぽの列を潰す」だけが違う。 潰された列は幅0pxの列として扱われ、その両側のすきまも一緒に消えます。
すきままで消えるのが効きます。列が0pxになってもすきまが残っていたら、右側に不思議な空白が残るところでした。両方消えるので、 残った子があらためて余りを分け直して、きれいに広がります。
使い分けは「中身が減ったときにどうしたいか」だけです。 カードが2枚しかない日でも画面いっぱいに広げたいならauto-fit、 カードの大きさを変えずに左から並べたいならauto-fill。子がいつも列の数だけあるなら、どちらを書いても同じです。
Grid は2次元、Flexbox は1次元。ここで使い分けが決まります
どちらを使うか迷ったときの答えは、仕様の1行目に書いてあります。「1つの軸を向いている Flexbox と違い、Grid は2次元の配置 ── たてとよこの両方でそろえたい場合 ── に向けて作られている」。
言いかえると、そろえたい方向が2つあるかどうかです。 カードを3列に並べて行の高さもそろえたいなら、たてとよこの2つなので Grid。 ボタンを横一列に並べてあいだの間隔だけ決めたいなら、1つなので Flexbox。
もう1つの見分け方は、マス目を先に決めるかどうかです。 Grid は親が先にマス目を作って、そこへ子を置きます。 Flexbox は子が並んだ結果として形が決まります。 だから Grid では子が1つも無くても、列と行はもう存在しています。 このページの見本で子の数を減らしても、マス目そのものは残っている ── その感じが、2つの違いです。
どちらかが新しくて、どちらかが古い、という関係ではありません。1本の並びに Grid を使っても動きますし、その逆も動きます。 ただ、書く量とあとから直す手間が変わります。
dense は、見た目の順番だけを変えます
grid-auto-flow: row denseと書くと、前に空いた穴に、うしろの小さい子が入ります。 すきまだらけだった並びが、 きれいに詰まります。
ただし、変わるのは目で見たときの並びだけです。仕様は「置く場所の指定は、音声などの目で見ない媒体での順番を変えない」と書いています。 読み上げソフトが読む順番も、タブキーで進む順番も、HTMLに書いた順のままです。
だから仕様は、ここだけ強い言い方をしています。「作者はこれらを、見た目のためだけの並べ替えに使うこと。 意味の順番をこれで作ったスタイルシートは、仕様違反である」。 順番そのものに意味があるなら、HTMLの並びを直すのが正しいやり方です。
目で見て3番目にあるボタンが、キーボードでは7番目に来る ── denseを使うと、こういうことが起こります。 飾りの並びに使うぶんには便利ですが、手順や順位のように「順番そのものが中身」のものには使わないでください。
決まりの出どころは、frの定義・すきまの扱い・auto-fitとauto-fill・ぶち抜きで列が足されること・ 目で見ない媒体での順番がW3C の CSS Grid Layout Module Level 1(新しいタブで開きます)、justify-itemsとalign-itemsの効く向きと初期値がCSS Box Alignment Module Level 3(新しいタブで開きます)です。 このページに出てくる数は、仕様の文を写しただけのものではありません。 実際のブラウザで測った値を持っていて、計算した列の数・列の幅・どのマスに入るかが実測と一致することを、 単体テストが毎回確かめています。