height: auto; の要素へ「もっと見る」機能を付ける

# はじめに
CSSアドベントカレンダー３日目はCSSに関する小ネタです。

記事一覧などのページで「もっと見る」ボタンを実装するときに、`height` へのCSSアニメーションのよくあるハマりポイントと私が実務で解決したときの実例を紹介したいと思います。

# 作りたいもの
「もっと見る」ボタンを押して続きが読める機能

「もっと見る」ボタンを押した後に元に戻る機能は考えないものとします。
また、本記事はCSSについての解説がメインなのでJSの実装などは適当もしくは記述しない場合があるので予めご了承ください。

# `height` が実数値のときの「もっと見る」ボタン
まずは、`height: auto;` じゃないパターンです。
閉じた状態と開いた状態のスタイルを書いて、`transition` の指定をするだけです。簡単。

```css
.content {
  transition: height 0.5s ease-in-out;
  height: 40px;
}

.content.open {
  height: 80px;
}
```

<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="js,result" data-user="ezawa800" data-slug-hash="xxbxxqp" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Open/Close content sample">
  <span>See the Pen <a href="https://codepen.io/ezawa800/pen/xxbxxqp">
  Open/Close content sample</a> by Sho Ezawa (<a href="https://codepen.io/ezawa800">@ezawa800</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# `height: auto;` にしたい
「もっと見る」ボタンを押したら内容をすべて表示するようにしたいケースでは、`height: auto;` を指定したいこともあります。

こんな感じです。

```css
.content {
  transition: height 0.5s ease-in-out;
  height: 40px;
}

.content.open {
  height: auto;
}
```

ただし、これには問題があり…

# `height: auto;` には `transition` が効かない！

<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="html,result" data-user="ezawa800" data-slug-hash="RwNwwZv" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Open/Close content sample with height:auto;">
  <span>See the Pen <a href="https://codepen.io/ezawa800/pen/RwNwwZv">
  Open/Close content sample with height:auto;</a> by Sho Ezawa (<a href="https://codepen.io/ezawa800">@ezawa800</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

こればかりは仕様なので仕方がない。
代案を考えます。

# `max-height` を使うと `transition` が効くようになる
よくある解決策がこちら。

```css
.content {
  transition: max-height 0.5s ease-in-out;
  max-height: 40px;
}

.content.open {
  max-height: 9999px;
}
```

<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="html,result" data-user="ezawa800" data-slug-hash="povooaN" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Open/Close content sample with max-height;">
  <span>See the Pen <a href="https://codepen.io/ezawa800/pen/povooaN">
  Open/Close content sample with max-height;</a> by Sho Ezawa (<a href="https://codepen.io/ezawa800">@ezawa800</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# ただし、まだちょっと問題が…
## 開いた状態の `max-height` の値をどうするか問題
開いたときに中身をすべて表示したい場合、文字数制限などがあればいいのですが、

1. 仕様上の問題で文字数制限を設定できない
2. あとから仕様が変わって文字数の上限が変わってしまいスタイルが崩れた

などが考えられます。

## `max-height: 9999px` のような指定の問題点
それならば絶対に中身がはみ出さない十分に大きな値を指定すれば！と思うのですが…

サンプルをみてもらうと分かるように、`transition` に指定した秒数と実際にアニメーションの速さが違ってしまいます。

再掲：
<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="html,result" data-user="ezawa800" data-slug-hash="povooaN" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Open/Close content sample with max-height;">
  <span>See the Pen <a href="https://codepen.io/ezawa800/pen/povooaN">
  Open/Close content sample with max-height;</a> by Sho Ezawa (<a href="https://codepen.io/ezawa800">@ezawa800</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

これは、`transition` はあくまで `max-height` を指定の秒数で変化させるため、例えば開いた状態の高さが 300px ならば `max-height` が 300px になった時点でコンテンツが開き切ってしまいます。

さらに、文字数によってアニメーションの速度がバラバラになってしまい、文字数が多い場合は速すぎる、文字数が少ない場合は遅すぎる、ということが起こる可能性があります。

アニメーションをこだわって作る場合にこれではちょっと困ります…。

# 解決案： オーバーレイを使う
そもそもアニメーションを付けるのは、ユーザーに「画面が変化したことを気づいてもらいやすくする」のが目的なので、必ずしも `height` に対してアニメーションを付ける必要はないです。

そこで、おすすめなのはオーバーレイを使ったパターンです。

`height` はアニメーションなしで変化させ、オーバーレイを徐々に透明にしていくことで、擬似的に徐々にコンテンツが現れたような表現にすることができます。

```css
.content .overlay {
  transition: opacity 1s ease-in-out;
  opacity: 1;
  
  pointer-events: none;
  background: linear-gradient(rgba(255, 255, 255, 0), #fff);
  position: absolute;
  top: 40px; /* = .content height */
  left: 0;
  width: 100%;
  height: 100%;
}

.content.open .overlay {
  opacity: 0;
}
```

<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="html,result" data-user="ezawa800" data-slug-hash="eYmYYjG" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Open/Close content sample with overlay">
  <span>See the Pen <a href="https://codepen.io/ezawa800/pen/eYmYYjG">
  Open/Close content sample with overlay</a> by Sho Ezawa (<a href="https://codepen.io/ezawa800">@ezawa800</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

上記のサンプルでは、閉じた状態ではオーバーレイは `overflow: hidden;` で隠れていますが、開く前からコンテンツの上のかぶせて置くことのも良いと思います。その際は、オーバーレイの下のコンテンツが選択できるように `pointer-events: none;` を付けておくと良いと思います。

# まとめ
- `height: auto;` だと `transition` が効かない
- `max-height` を使うパターンだと十分じゃないケースもある
- 解決策としてオーバーレイを使うパターンがおすすめ

# その他の解決策
アニメーションの話は、「すべてのケースはこれで解決！」ということは無いと思うので、

- 他にこんな方法で解決した
- オーバーレイを使った方式だとこんな問題点があるかも

と言ったアイディア・ご意見があればコメントでもらえると嬉しいです！