HTMLやCSSのレイアウトの歴史を振り返る

この記事は [FUJITSU Advent Calendar 2017 part2](https://qiita.com/advent-calendar/2017/fujitsu_part2) の15日目です。

HTML, CSSでのレイアウトの歴史を振り返りつつ、最新のレイアウト方法についてまとめたいと思います。

## Tableレイアウト

Tableレイアウトと呼ばれる全体レイアウトを行う手法があります。
10年以上前から使われている方法で、レイアウト方法としては一番古いのではないかなと思います。
Tableレイアウトでは以下のようにHTMLの`table`タグを使用してレイアウト位置を決めていきます。
`table`タグは本来表を作成するためのものとして用意されています。表として表示したいもの以外を`table`タグで設定することはタグの利用用途として間違っていることや、アクセシビリティー観点からも良くないこともあり、現在ではあまり利用されていません。

DEMO:[Tableレイアウト例](https://codepen.io/misako/pen/WdeVdP)

```html:table.html
<!--
  サイト表示全体をtableタグで実施
  さらに細かいレイアウトはtableタグを入れ子構造にしていきます。
 -->
<table id="layout">
  <tr id="header">
    <td colspan="3">ヘッダー</td>
  </tr>
  <tr id="main">
    <td class="nav">左カラム</td>
    <td class="article">
      <p>中央カラム</p>
      <!-- 本来用途の表組み -->
      <table class="like">
        <thead>
          <tr>
            <td>ジャンル</td>
            <td>好きな食べ物</td>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>肉類</td>
            <td>牛肉</td>
          </tr>
          <tr>
            <td>野菜類</td>
            <td>にんじん</td>
          </tr>
        </tbody>
      </table>
    </td>
    <td class="aside">右カラム</td>
  </tr>
  <tr id="footer">
    <td colspan="3">フッター</td>
  </tr>
</table>
```

## CSS float Property

Tableレイアウトの次に出てきたのが`float`です。
こちらはcssのプロパティで、指定要素を左もしくは右に寄せて配置し、後に続く要素を回り込ませることができます。
この機能を使用して、カラムを回り込ませる形でレイアウトをしていました。これは今でも現役のサイトも多いのではないでしょうか。
`float`を使用したままですと本来回り込ませたくない要素（例だとフッター）まで回り込みが発生してしまうため、cleafix[^1]という方法でカラムの回り込みを解除します。
ブラウザごとに挙動が違うこともあり、clearfixはいろいろな方法が模索されていました。（例では`#main`内で使用しています）
[clearfixの最新版 -フロート関連やマージン相殺の不具合を解決するモダンブラウザ用clearfix](http://coliss.com/articles/build-websites/operation/css/css-latest-clearfix.html)

一番使い慣れているこの方法ですが、この要素自体も特にレイアウトをするためのものではなく、要素のもつ意味合いとやりたいことがずれています。また、`div`タグは意味を持たないため、それぞれの要素についてHTML解析上では意味が読み取れない（＝アクセシビリティー低下）という問題も持っています。
モバイルデバイスにも対応しづらいですね。。

DEMO:[floatレイアウト例](https://codepen.io/misako/pen/YYzKpJ)

```html:float.html
<div id="header">ヘッダー</div>
<div id="main">
  <div class="nav">左カラム</div>
  <div class="article">中央カラム</div>
  <div class="aside">右カラム</div>
</div>
<div id="footer">フッター</div>
```

```css:float.css
...
#main::after{
  /*
    この下3つはcleafixです。
    この例ではあまりブラウザ間の挙動違いは考慮していません。
  */
  content: "";
  clear: both;
  display: block;
}
#main > div {
  float: left; /* #main配下の要素すべてをfloatします */
  min-height: 800px;
}
...
```

## CSS Flexible Box Layout Module

HTML5およびCSS3が実装されるにつれて`div`だらけになっていたHTML界隈も様子が変わってきました。`header`タグ、`footer`タグ、`main`タグなど、今まで`div`で実装してきたものが正式なhtmlタグで実装できるようになってきました。
また、CSS Flexible Box Layout Module（`flexbox`）が登場[^2]し、`float`ではなく`flexbox`を使おう！という動きになってきました。
`flexbox`は名前の通り可変のボックスで、コンテンツのレイアウトをスクリーンサイズやデバイスのディスプレイサイズに柔軟に対応させることができます。
Flexboxを使用することでHTMLの読み上げ順（音声読み上げなど）やソースでの記載順に依存せずレイアウトを作ることができます。
HTMLの構造と見た目を分離することができてとても良いですね。

例として３カラム＋サイズが小さくなるとレイアウト変更をするものを解説します。
(CSSの設定に関しては[CSS flexible box の利用 | MDN web docs](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes)からお借りしました)
floatの例でもレイアウト変更まで含めようかと思ったのですが、面倒で断念。。。

DEMO:[flexboxレイアウト例](https://codepen.io/misako/pen/GObQjJ)

```html:flexbox.html
 <header>ヘッダー</header>
 <main role='main'>
   <article>中央カラム</article> 
   <!--
      articleがtableやfloatの時と違って
      先頭に来ているのはポイント！
      floatでは中央 左 右の順になってしまいますが、
      flexboxを使うと順番を変えられます。
    -->
   <nav>左カラム</nav>
   <aside>右カラム</aside>
 </main>
 <footer>フッター</footer> 
```

```css:frexbox.css
main {
  min-height: 800px;
  margin: 0;
  padding: 0;
  display: flex;  /* ここでflex指定。子要素は全てflexアイテムになります。 */
  flex-direction: row;
  /*
    flexアイテム（nav, article, aside）がどの方向に向かって並ぶかを設定。
    rowは文字が入力されたときに向かう方向（通常左から右）に向かって並びます。
    3カラムは左から並べたいのでrowを設定。
  */
}
main > article {
  margin: 4px;
  padding: 5px;
  border: 1px solid #cccc33;
  border-radius: 7pt;
  background-color: #dddd88;
  flex: 3 1 60%;
  /*
    flex-grow, flex-shrink, flex-basisをまとめて設定。
    flex-grow:
      flexコンテナ内にあるflexアイテムの伸びる倍率を指定。
      この例では3つのコンテナで合わせて5指定しているため、余っているスペースのうち3/5をarticleに割り当てます。
      実際の幅は"articleそのものの幅 + 余っているスペース * 3/5"になることに注意。
    flex-shrink:
      flexコンテナ内にあるflexアイテムの縮む倍率を指定。
      flex-growでは数字が大きいと表示の占める割合が大きくなりますが、flex-shrinkでは数字が大きいと割合が小さくなります。
      この例では3つのコンテナで合わせて13指定しているため、足りない（縮める）スペースのうち1/13をarticleに割り当てます。
       実際の幅は"articleそのものの幅 - 足りない（縮める）スペース * 1/13"になることに注意。
    flex-basis:
      ベースとなるwidth or heightを指定する。
      flex-directionの指定によってどこの幅を指定するかが異なる。
      この例ではflex-directionにrowを指定しているので、横幅（width）を指定することになる。
      columnを指定していた場合は縦幅（height）指定になるので注意。
      60%指定のため、全体100%のうち60%幅をarticleが占める。
  */
  order: 2;
  /*
    flexアイテムの表示順番の指定。
    この例ではflexアイテムに1,2,3を指定しており、articleには2を指定しているので2番目（navの隣）に表示される。
    マイナス値なども設定できるため、相対評価になることに注意。
    z-index値みたいな感じですね。
  */
}
...

@media all and (max-width: 640px){
  /*
    640px以下になった場合のレイアウト指定。
    モバイルデバイスなど
  */
  main {
    flex-direction: column;
    /*
      縦レイアウトに変更
    */
  }
  main > article, main > nav, main > aside {
    order: 0;
    /* htmlで記述された順番に従うため、orderは0に初期化します。 */
  }
  main > nav, main > aside, header, footer {
    min-height: 50px;
    max-height: 50px;
    /*
      nav, aside, header, footerは高さを50pxにします。
      hightプロパティ設定では50pxになりません。
      ちょっと理由はわかりませんでしたが、上の方でheaderとfooterにmin-height: 100pxを指定してるからかな・・・？
    */
  }
}
```
## CSS Grid Layout

Flexboxよりも新しいレイアウトの仕様であるCSS Grid Layout[^3]です。
昔行っていたTableレイアウトに近い考え方で、かつCSSのみでレイアウトが可能です。
Grid Layoutでは画面をグリッド（格子）状に分割してレイアウト箇所を指定していきます。
Tableレイアウトでも格子の枠をどのように使用するか指定してレイアウトを実施していたため、似たような考え方になります。
TableレイアウトではHTML上でレイアウト指定をしていましたが、Grid LayoutではCSSのみでレイアウトすることができるという点が良い点です。
また、Flexboxとの違いで言うと、並べたいカラムを囲うタグ（前の例の`#main`）が不要になることと、（通常はあまりしないとは思いますが）要素間が重なった指定も可能です。

ここではFlexboxで行った３カラム＋サイズが小さくなるとレイアウト変更する実装方法と、要素が重なった表示方法について例を記載したいと思います。

### Grid Layoutレイアウト例（3カラム+サイズ変更）

DEMO:[Grid Layoutレイアウト例（3カラム+サイズ変更）](https://codepen.io/misako/pen/OzPjRj)

```html:grid.html
<header>ヘッダー</header>
<!--
  flexboxの時と違い、mainタグがありません。
  このように、横並びにしたい要素があったとしても、
  わざわざタグで囲わなくても並べられるのが、
  Grid Layoutのメリットでもあります。
-->
<article>中央カラム</article> 
<nav>左カラム</nav>
<aside>右カラム</aside>
<footer>フッター</footer> 
```

```css:grid.css
body {
  display: grid;
  /* Grid Layoutを適用したい親要素（今回はbody）にdisplay: grid;を設定 */
  grid-template-rows: minmax(100px, auto) minmax(800px, auto) minmax(100px, auto);
  /*
    行の数と幅を指定。
    minmax関数は第一引数に最小値(幅)、第二引数に最大値（幅）を指定することで、その間でしかサイズが変更されなくなります。
    この例では1行目と3行目はminを100px、maxをautoにしているため、最少で100pxの縦幅、最大値は指定していないことになります。
    2行目は最少800pxまでしか縦幅が縮みません。
    空白を入れて3つ指定していることから、3行の定義をしていることがわかります。
  */
  grid-template-columns: 1fr 3fr 1fr;
  /*
    列の数と幅を指定。
    frという単位はGrid Layoutで利用できる単位で、全体幅のうちどのくらいの幅を各列が使えるかを定義しています（行でも使えます！）。
    この例では1列目と3列目は1frで2列目が3frのため、1列目、3列目は1/5の幅、2列目は3/5の幅を使用します。
    frで指定すると、windowのサイズに合わせて自動で幅が指定の倍率に調整されます。
  */
  grid-template-areas:
    "header header  header"
    "nav    article aside"
    "footer footer  footer";
  /*
    grid-template-rowsとgrid-template-columnsで指定した枠（グリッド）にそれぞれ名前を付けることができます。
    " "で囲ったのが1行。" "内に空白で区切って行ごとの名前を書きます。
    重複している名前は同じエリアとみなされます。また、空白は何個つかっても影響ありません。
    この例では3 * 3の行列を定義しています。
    1行目、3行目はすべてヘッダーに使いたいので、headerという名前を全部のグリッドにつけています。このようにすると1行目がすべて同じグリッドとみなされます。
    もし１列目をヘッダーから抜かしたい場合、以下のように"."を使うとその指定箇所はエリアに指定されません。
      ". header header";
    2行目は3つに分けるので、それぞれのグリッドに名前を付けています。
    HTMLの定義では中央カラムが左カラムよりも上に来ていますが、レイアウトは中央に持ってきたいので、中央にarticleという名前を付けています。
    名前は任意なので、aとかでもOKですが、わかりづらいのでそれぞれの役割に合った名前を付けることをお勧めします。
  */
  /*
    grid-template-*をまとめて指定することもできます。
    例は別のpenで。
  */
  grid-gap: 4px;
  /*
    各グリッド間のmargin指定です。
    以下の２種類指定できます。
    grid-gap: "縦のマージン" "横のマージン";
    grid-gap: "縦&横のマージン";
  */
}
...
header {
  grid-area: header;
  /*
    bodyで指定したgrid-template-areasのうち、
    指定したいエリアを設定。
    これを書くだけで位置が勝手に決まるので便利。
  */
}
...

@media all and (max-width: 640px){
  /*
    640px以下になった場合のレイアウト指定。
    モバイルデバイスなど
  */
  body {
      grid-template-rows: minmax(50px, auto) minmax(800px, auto) repeat(3, minmax(50px, auto));
    /*
      repeat関数は中に記載した内容を繰り返し指定します。
      以下のように指定。
      repeat("繰り返したい回数", "繰り返し内容");
      今回の例では、最小の行間を50px、最大は指定しない設定を３回繰り返し設定します。
      repeat関数もGrid Layoutのみ指定可能です。
    */
    grid-template-columns: 1fr;
    /* １列のみになるように指定。 */
    grid-template-areas:
      "header"
      "article"
      "nav"
      "aside"
      "footer";
    /* 各行にヘッダーからフッターまで並ぶように指定。 */
  }
}
```
### Grid Layoutレイアウト例（grid-template使用例）

上記例ではgrid-template-*をバラバラに指定しました。
grid-templateでまとめて指定することも可能です。

DEMO:[Grid Layoutレイアウト例（grid-template使用例）](https://codepen.io/misako/pen/xpGONM)

```css:gridLayoutTemplate.css
body {
  display: grid;
  grid-template: [header-top] "header header  header" minmax(100px, auto) [header-bottom]
                 [main-top]   "nav    article aside"  minmax(800px, auto) [main-bottom]
                 [footer-top] "footer footer  footer" minmax(100px, auto) [footer-bottom]
                   / 1fr 3fr 1fr;
  /*
    gird-template-*をまとめて指定した例。
    以下のような書き方になります。
    [各行の上側の名前（任意の名前を記載）] "各行のgrid-template-areasに書いていた内容" 各行のgrid-template-rowsに書いていた内容 [各行の下側の名前] / 各列の指定（全部まとめて記載）;
    見慣れないとわかりにくいので、まずはgrid-template-*の形式で書いてみて、
    後からまとめた方がいいかもしれません。
  */
  grid-gap: 4px;
}
```

### Grid Layoutレイアウト例（要素を重ねて表示）

要素を重ねて設定する場合、grid-template-areasの指定はいらない（というか使えない）と思います。

DEMO:[Grid Layoutレイアウト例（要素を重ねて表示）](https://codepen.io/misako/pen/ZvGpym)

```css:gridLayoutOverlaid.css
body {
  display: grid;
  grid-template: minmax(100px, auto) minmax(800px, auto) minmax(100px, auto) / 1fr 3fr 1fr;
  /*
    要素の重なり合わせをさせるので、area指定は無くしました。
    この場合、以下のような指定になります。
    grid-template: <grid-template-rowsに指定する値> / <grid-template-columnsに指定する値>;
  */
  grid-gap: 4px;
}
...
header {
  grid-row: 1 / 2;
  grid-column: 1 / 4;
  /*
    grid-template-areasを指定していないので、grid-rowやgrid-columnを使って
    どの行、どの列に要素を当てはめるか指定します。
  
    行番号
    |
    v   1        2        3        4  <-----列番号
        |        |        |        |
    1 --+--------+--------+--------+--
        |        |        |        |
    2 --+--------+--------+--------+--
        |        |        |        |
    3 --+--------+--------+--------+--
        |        |        |        |
    4 --+--------+--------+--------+--
        |        |        |        |
  
    grid-row: <開始行の番号> / <終了行の番号>;
    (例の場合、行番号1から2の間に要素を置く)
    grid-column: <開始列の番号> / <終了列の番号>;
    (例の場合、列番号1から4の間に要素を置く)
    3 * 3のグリッドの場合、行・列番号はそれぞれ+1になることに注意。
  */
}
...
```

CSS Grid Layoutは指定項目がとても多く、ここでは書ききれていないこともたくさんあります。詳細は以下のようなサイトがとても詳しく解説していますのでご覧ください。
[CSS Grid Layout を極める！（基礎編）](https://qiita.com/kura07/items/e633b35e33e43240d363)
[CSS Grid Layout を極める！（場面別編）](https://qiita.com/kura07/items/486c19045aab8090d6d9)
[HTML5 Experts.jp | これからのCSSはmargin禁止！？CSSグリッドレイアウトやコンポーネント指向なCSSについて、矢倉さんに聞いてきた！](https://html5experts.jp/shumpei-shiraishi/24439/)

## Flexible Box Layout, Grid Layoutをどう使い分ける？？

Flexible Box Layout、 Grid Layoutと新しいレイアウトの仕様を紹介してきましたが、どっちをどう使えばいいの？と思われるかもしれません。
個人的な見解ですが、Flexible Box Layoutは横並びや縦並びを柔軟に動かすことができ、横幅からはみ出たら折り返して下の行にボックスを続けて表示する等できるため、コンテンツのカードデザイン[^4]に向いていると思います。
また、Grid Layoutは上記で示した通り、レイアウトを柔軟に設定できることから、サイト全体のレイアウトに向いていると思います。
結論としてはGrid Layoutをサイト全体のレイアウト、Flexible Box Layoutをカードデザイン等サイト内の一部のレイアウトに使う、という使い分けが良いと思います。

とはいえ、Grid LayoutはIEで仕様が古い[^3]など、各種ブラウザ対応が必要な場合導入が難しいかもしれません。
今後のブラウザの実装状況を見つつ、使えるところに使っていくという対応が必要そうです。

## 参考資料

### Flexbox関連

[【CSS】Flexboxのプロパティ（flex-direction、flex-wrap、flex-flow、order）を紹介します | Scene Live](http://scene-live.com/page.php?page=57)
[【CSS】Flexboxのプロパティ（flex-grow、flex-shrink、flex-basis、flex）を紹介します | Scene Live](http://scene-live.com/page.php?page=58)
[CSS flexible box の利用 | MDN web docs](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes)
[CSS3 Flexbox の各プロパティの使い方を詳しく解説 | coliss](http://coliss.com/articles/build-websites/operation/css/css3-flexbox-properties-by-scotch.html)
[flexbox-grow, flex-shrink, flex-basis について | メモを揉め](http://memowomome.hatenablog.com/entry/2016/09/06/080000)
[CSS Flexible Box Layout Module Level 1](https://www.w3.org/TR/css-flexbox-1/)

### Grid Layout関連

[grid-template | MDN web docs](https://developer.mozilla.org/ja/docs/Web/CSS/grid-template)
[CSS Grid Layout Module Level 1](https://www.w3.org/TR/css3-grid-layout/)
そのほかGrid Layoutの解説内で記載したURL


[^1]: cleafixの代わりに`display:flow-root;`が提案されているそうです。現在(2017/12/15時点)ではChromeとFirefoxのみサポートされています。（参考：[Can I use ... | display: flow-root](https://caniuse.com/#search=flow)）このプロパティを使うとさらに簡単にcleafixが可能です。(DEMO:[flow-root例](https://codepen.io/misako/pen/rpOawq))まだWorking Draft(作業草案)のようなので、今後仕様が変わる可能性があります。
[^2]: CSS Flexible Box Layout Moduleは現在(2017/12/15時点)、W3C Candidate Recommendation（W3C勧告候補）です。しかしながら、昔から提案されていたこともあり、ほぼすべてのブラウザで実装済みです。各ブラウザの最新実装状況は[Can I use ... | CSS Flexible Box Layout Module](https://caniuse.com/#search=flex)を確認してください。
[^3]: CSS Grid Layoutは現在(2017/12/15時点)、W3C Candidate Recommendation（W3C勧告候補）です。主要なブラウザでサポートされていますが、Opera MiniとUC Brower for Androidではサポートされていないようです。また、IEは古い仕様のGrid Layoutがサポートされているようなので、今回紹介した記載方法と異なる可能性があります。各ブラウザの実装状況は[Can I use ... | CSS Grid Layout](https://caniuse.com/#feat=css-grid)を確認してください。
[^4]: 例：[カード型デザイン | Tech Note](https://tech-camp.in/note/pickup/4367/#i-2)