【CSS】要素の縦中央揃えをいい加減攻略したい。



# はじめに
この記事は [株式会社ピーアールオー（あったらいいな！を作ります） Advent Calendar 2019](https://qiita.com/advent-calendar/2019/pro-japan1) の5日目の記事です。
突然の初学者向けみたいな内容ですが、よろしくお願いします。

今年は一年かけて、Angularを用いたWebサイトの開発に邁進していました。
Angular、モジュールを分割することで中々気持ちよく開発できるフレームワークだったのですが、モジュール分割をしてる都合上どうしてもCSSを弄らなければならないシーンが多く、**弱点：CSS**な自分は結構躓いてしまうシーンが多かったです。
中でも毎回悩ませてくれたのが、タイトルにもなっている**縦中央揃え**です。

## 縦中央揃えとは
名前の通り、要素を縦中央に沿って並べることです。
Angularなどのフレームワークではモジュールが独立しているため、要素のサイズを事前に決定することができない事が多いため、意外と多用することになります。

理想としてはこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/d34eb772-c8a6-9ab2-4328-6aefdbae273b.png)

ただ、どうにもこの縦中央揃え、世に書かれているおすすめコードを試してみても効いたり効かなかったりして、最終的に
**「色々試してたら書き散らかしたコードが上手い具合に効いて縦中央に揃ったわ！ヨシ！」**
となりがちだったので、今回はこの場を借りて巷でよく聞く縦中央揃えの手法を実際にいじり倒してみようかと思います。

## 確認環境
- Google Chrome 78.0.3904.108
- Firefox 70.0.1 
- Microsoft Edge 44.18362.449.0
- Internet Explorer 11.476.18362.0

(スクリーンショットはGoogle Chromeを使用)

## ベースコード
<details><summary>ちょっと長いので折り畳み</summary><div>

```sample.html
<head>
  <style>
    .parent-box {
      display: block;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
    }

    .child-box {
      display: inline-block;
      width: 100px;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
    }
    .child-box.one {
      font-size: 16px;
      height: 50px;
    }
    .child-box.two {
      font-size: 24px;
      height: 80px;
    }
    .child-box.three {
      font-size: 12px;
      height: 30px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>
## 事前知識：inline要素の縦位置の相互作用
縦中央揃えに行く前に、まずinline要素を並べたときにどんな相互作用が発生するか理解しておく必要があります。

では、まずなんも考えずにベースコードを実行して、何が表示されるか見てみましょう。
![no-style.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/4310654f-840f-935d-85dc-cdfc6a6a95a8.png)
…一番上にきちっと揃うのを期待してましたがガッタガタですね。個人的にもうこの時点で**なんでやねん！**とベタな突っ込みをしてしまいそうになるのですが、一旦落ち着いて1本補助線を引いてみましょう。
![baseline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/78f54504-14ff-17d6-163e-403df1f601d1.png)

フォントサイズの違う3つの文字がきっちりラインで揃ってるのがわかると思います。
このラインが**baseline**です。vertical-alignによって位置設定がされていないinline要素は、「alphabetic」というアルファベットベースのbaselineによって、位置が規定されます

もう少しbaselineがどういう動きをするか、色々試してみましょう。
例えば、画像・二行以上の文章・要素の中身なしだと…？
![iroiro.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/4079b42f-46fe-b827-f87b-d4e6522d1256.png)
どうやら、画像の下・一番下の行の下・要素自体の一番下がbaselineとして設定されるようです。
直感的といえば直感的な揃い方ですかね…？

inline要素はこのように同じラインにある子要素の影響を受ける可能性のある要素ということを覚えておく必要があります。

さて、この知識を武器に縦中央揃えを攻略していきましょう。

# よく聞く縦中央揃えの手法
今回は以下の5つの縦中央揃えの手法を試してみたいと思います。

- vertical-align: middle;
- line-height: (親要素の高さ);
- margin: auto 0;
- transform
- display: table-cell;

## vertical-align: middle;
名前が**縦中央に揃えられるぜ感**を出してるせいで、最初に適当に試して玉砕しがちな手法ですね。

まず、vertical-alignは子要素同士の位置関係性を定義するプロパティです。
したがって、**子要素に対して**付与するプロパティとなります。

では試しに子要素につけてみましょう。
![middle1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/0e2fc2f0-aee0-a827-5aed-39490f2076ce.png)
お、未適用の状態に比べるとbaselineの制約が消えて縦中央に揃いましたね。いやーめでたｓ**コレジャナイ。**
はい。やりたいのは親要素に対する中央揃えなので、子要素の縦中央だけ合わせても仕方ないですね。
子要素の中央揃えも必要になりますので、``vertical-align: middle;`` は他の中央揃えの手法と組み合わせて使うのが基本のようです。

ただし、どれかの子要素の縦の中心が親要素の中心と一致している場合 （例：親要素の高さ＝子要素の高さ）、vertical-alignのみでも他の要素の縦中央揃えが可能です。
試しにChild3の縦中心を親に合わせてみたのが以下です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/1f6455d9-2923-8c21-0b1a-ef68bc9bdf72.png)
Child3のボックスに引きずられて、Child1,2どちらも縦中央に揃っています。
このやり方は一つでも高さを指定できるinline-blockが入っていれば有効ですので、意外とこれだけでなんとかなるシーンは多そうです。

## line-height: (親要素の高さ);
テキストがline-heightに対して縦中央に配置されることを利用して、無理やり縦中央揃えに利用してしまおうという、どこなくパワーを感じる手法ですね。

まあ、まずは何も考えずに適用してみましょう。テキストに対するプロパティなので適用は「親要素」「子要素全て」のどちらでもOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/4f5b9e37-bd0d-2b98-fb1b-cd5b3063e220.png)
**あっ、はい。** 
inline-blockだと内部テキストにデザインが追従しないので文字だけが吹っ飛んでしまうようですね…。また、Child2のbaselineに引きずられて、Child1, Child3の文字が中央からずれているようです。

では、子要素の``display: inline-block;``を``display: inline;``に変更して、子要素同士が縦中央に並ぶように``vertical-align: middle;``を追加してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/ed1c3f61-b0f6-6eb6-debc-d19e45ac9703.png)
縦中央に並びましたね！
元のinline-blockを利用する想定とは違いますが、inlineの子要素のみならこれでも行けそうです。
ただし、line-heightは値として"100%"が使用できないので**親要素の高さが固定であることが前提**となるのに注意する必要があります。

## margin: auto 0;
自動的に揃えたい時にautoって単語は非常に心地よく耳に響きますよね。
flexboxのmarginの上下にautoを設定するとうまい具合に~~（よくわかってないの意）~~中央揃えになってくれる機能を利用したものです。

もう説明からして求めているものですが、落ち着いて適用してみましょう。
親要素に、``display: flex;``、子要素に``margin: auto 0``を適用しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/669f805c-2bb1-10fe-695d-02c97631cacc.png)
かなりきれいな中央揃えになりました！baselineの影響も受けていないようです。

なにかすんなり行き過ぎて悔しいので、子要素がinline要素の場合も試してみましょうか。
全ての要素をinlineにして確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/217f2bfa-0e1f-82f0-9164-821cf29736df.png)
悔しいことにこちらもきれいに縦中央揃えになるようです。

Flexboxを利用して問題のないデザインであれば縦中央揃えは``margin: auto 0;``が安定しているようです。

## transform
失敗が続きパラメータによる自動整列が信用できなくなってくる頃に使いたくなってくる、ちゃんと要素をずらして縦中央揃えを実現する手法です。
以下の三つの要素を子要素に適用することで縦中央揃えを実現します。

```
{
      position: relative;
      top: 50%;
      transform: translateY(-50%);
}
```

上記CSSの挙動としては、
1. **親要素の高さの50%**の位置を始点に子要素を描画する(``top: 50%;``)
2. **子要素の高さの50%**だけ子要素を上に移動させる(``transform: tranlateY(-50%);``)
といった感じです
50%の意味が異なるのでちょっと混乱しますが、動作を書きだしてみるとシンプルで間違いのない縦中央揃え方法ですね。

では、子要素に適用してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/92c397f8-d35a-09c6-ef54-61a6bdd433aa.png)
**んん？** なぜかChild1, Child3が微妙に中央からずれてしまっていますね…。baselineに沿っているような感じでもないですし…。
確認のために、``transform: tranlateY(-50%);`` を外してみましょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/97db9aea-02d2-9e54-1729-f9f96e125604.png)
なるほど、どうやらこれもbaselineの影響のようです。``transform: tranlateY(-50%);`` で綺麗に中央に寄るには全子要素の一番上が親要素の中央線と同じ場所になければなりません。
それが達成できるように、子要素を上揃えにする``vertical-align: top;``を追加して改めてやりなおしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/d34eb772-c8a6-9ab2-4328-6aefdbae273b.png)
よし！きれいに縦中央揃えになりました！。

では、inline要素の場合はどうでしょうか？全ての要素をinlineにしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/72d61f0f-64a1-adf5-2c61-18ccf41d688e.png)
うーん、どうやら``transform: translateY(-50%);``が機能していないようです。inline要素は高さを持っていませんので、その影響だと思われます。

今回の様にinline-blockで構成されている子要素ならば問題ないですが、inline要素の場合は別の縦中央揃えを利用するほうが良さそうです。

## display: table-cell;
縦中央揃えって言葉から中々連想が出来ない秘策が``display: table-cell;``です。
基本的な使い方は、親に以下の要素を追加する形です。

```
{
    display: table-cell;
    vertical-align: middle;
}
```

では試しにやってみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/4c363658-7381-135b-d694-71ef8774f369.png)
あ、なんかもう見慣れた感じのズレですね。余計な事は言わず粛々と子要素に``vertical-align: middle;``を追加しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/738ec3a1-c4de-ff89-131d-8f333feaec56.png)
綺麗に縦中央揃えになりました！
このまま子要素をinlineにしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378060/9da33198-f61f-077b-6e1b-8bc234d32f9c.png)
inlineにしても問題なく縦中央揃えになるようです！

かなりお手軽に中央揃えが出来るtable-cellですが、ちょっと特殊な要素でもありますので使う際は意識しておく必要がありそうです。
※ 以下の記事がtable-cellの制約について詳しく解説しています。
[display:table-cell;を安易に使うべきでない理由いろいろ](https://qiita.com/sawadays0118/items/4c329fd05cdff14ffebc)

# まとめ
## 各縦中央揃えについて

### vertical-align: middle
- 通常は親要素に対して縦中央揃えはできない。
    - 親要素と縦の中心が一致している子要素があれば可能。

##### ベースコードへの適用例
<details><summary>コード</summary><div>

```sample-vertical.html
<head>
  <style>
    .parent-box {
      display: block;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
    }

    .child-box {
      display: inline-block;
      width: 100px;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
      vertical-align: middle;
    }
    .child-box.one {
      font-size: 16px;
      height: 50px;
    }
    .child-box.two {
      font-size: 24px;
      height: 80px;
    }
    .child-box.three {
      font-size: 12px;
      height: 194px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>
### line-height: (親要素の高さ) 
- 縦中央揃えが適用されるのはinline要素のみ。
- 親要素の高さが固定長である必要がある
- 各要素のbaselineが異なる場合、vertical-align: middleを適用する必要あり。

##### ベースコードへの適用例
<details><summary>コード</summary><div>

```sample-line.html
<head>
  <style>
    .parent-box {
      display: block;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
      /* ボーダーの影響を受けるため、3px * 2だけ小さくする必要がある */
      line-height: 194px;
    }

    .child-box {
      display: inline;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
    }
    .child-box.one {
      font-size: 16px;
    }
    .child-box.two {
      font-size: 24px;
    }
    .child-box.three {
      font-size: 12px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>
### margin: auto 0;
- inline要素、inline-box要素どちらでも正しく縦中央揃えを実行することができる
- 親要素をflexにする必要があるため、適用できるかは要検討。

##### ベースコードへの適用例
<details><summary>コード</summary><div>

```sample-margin.html
<head>
  <style>
    .parent-box {
      display: flex;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
    }

    .child-box {
      display: inline-block;
      width: 100px;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
      margin: auto 0;
    }
    .child-box.one {
      font-size: 16px;
      height: 50px;
    }
    .child-box.two {
      font-size: 24px;
      height: 80px;
    }
    .child-box.three {
      font-size: 12px;
      height: 30px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>
### transform
- inline-block要素は縦中央揃えにすることが可能
    - 各要素のbaselineが異なる場合、vertical-align: top; で調整する必要がある。
- inline要素はうまく縦中央揃えにすることができない。

##### ベースコードへの適用例
<details><summary>コード</summary><div>

```sample-transform.html
<head>
  <style>
    .parent-box {
      display: block;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
    }

    .child-box {
      display: inline-block;
      width: 100px;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
      position: relative;
      top: 50%;
      transform: translateY(-50%);
      vertical-align: top;
    }
    .child-box.one {
      font-size: 16px;
      height: 50px;
    }
    .child-box.two {
      font-size: 24px;
      height: 80px;
    }
    .child-box.three {
      font-size: 12px;
      height: 30px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>


</div></details>
### display: table-cell;
- inline要素、inline-box要素どちらでも正しく縦中央揃えを実行することができる
- 親要素をtable-cellにする必要があるため、適用できるかは要検討。

##### ベースコードへの適用例
<details><summary>コード</summary><div>

```sample-table.html
<head>
  <style>
    .parent-box {
      display: block;
      width: 350px;
      height: 200px;
      border: 3px solid rgba(100, 0, 0, 1);
      background: linear-gradient(
        180deg,
        #ff9999 0%,
        #ff9999 50%,
        #ffff99 50%,
        #ffff99 100%
      );
    }

    .child-box {
      display: inline-block;
      width: 100px;
      border: 3px solid rgba(0, 0, 100, 1);
      background-color: rgba(100, 100, 255, 0.3);
      position: relative;
      top: 50%;
      transform: translateY(-50%);
      vertical-align: top;
    }
    .child-box.one {
      font-size: 16px;
      height: 50px;
    }
    .child-box.two {
      font-size: 24px;
      height: 80px;
    }
    .child-box.three {
      font-size: 12px;
      height: 30px;
    }
  </style>
</head>
<body>
  <div class="parent-box">
    <span class="child-box one">Child1</span>
    <span class="child-box two">Child2</span>
    <span class="child-box three">Child3</span>
  </div>
</body>
```

</div></details>
### 子要素の属性による縦中央揃えの効きの有無

| 手法 | inline-block要素 | inline要素 |　親要素制約 |
|:-:|:-:|:-:|:-:|
| vertical-align: middle (高さ指定) | 〇 | △※ | なし |
| line-height: (親要素の高さ) | × | 〇 | 固定長が指定できること |
| margin: auto 0; | 〇 | 〇 | 親表示要素がflexであること |
| transform | 〇 | × | なし |
| display: table-cell; | 〇 | 〇 | 親表示要素がtable-cellであること |

※ 縦中央が親と一致するinline-blockが混在している場合は、縦中央揃えが可能。

## 感想
いじり倒してる内に万能の解法が見つからないかな、と淡い期待を持っていたのですが、やはり手法が乱立してるだけあって一長一短、中々簡単にはいかないようです…。
ただ、これなら効く、効かないというのが割とはっきりしたので、今後縦中央揃えにチャレンジしなければならなくなっても上手く立ち回れそうです。
**あと、とりあえずCSSで記事書くのはめっちゃしんどい事はわかりました。**