calc()関数、vw単位を使った絶対に崩れないCSSの書き方

# もう画像に逃げないコードを書こう！

![スクリーンショット 2022-06-11 7.28.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654728/d0c5d85d-cf20-e554-7d02-9388aed1fce4.png)


見た目の凝ったデザインをコーディングする際、表示領域（以下：わかりやすく`画面幅`と書きます）の拡大縮小に合わせてレイアウトが崩れてしまって、デレクターさんやデザイナーさんから、「デザイン通りになんとかならない？」と言われた経験ありませんか？

そんなときは`vw単位`と`calc()関数`を使うとかんたんに画面幅に合わせてレイアウトを保ってくれる要素を作ることが出来ます。
「いや、流石に無理でしょこんなん…画像にしちゃいましょ？」と逃げてる人、メディアクエリを大量に設定してごまかしている人、ちょっと理解するのが難しいですが、以下を試してみてください！

## 結論を急ぎたい人へ

前置きはいいから結論を教えて！という人へ。
こちらの計算式にを使うと`表示させたい画面幅`に対して`表示したいピクセル数`を指定できます。

`calc( 表示させたいピクセル数 vw/ 表示させたい画面幅 ÷100)`

ん…分かりづらいですね、例を見てもらったほうがわかりやすいと思います。↓

例）

- 画面幅が`375px`のとき高さ(height)が`540px`となるようにしたい。
`height: calc(540vw / 3.75);`

- 画面幅が`1400px`のとき幅(width)が`766px`になるようにしたい。
`width: calc(766vw / 14);`

- 画面幅が`1920px`のとき上下の余白(margin)が`66px`になるようにしたい。
`margin: calc(66vw / 19.2) auto;`

というように、これらを駆使することで`画面幅〇〇pxのとき〇〇px`という相対的なサイズを指定できる様になります。

例えば、下記のように画面幅に合わせて丸の位置や、テキストのサイズだけでなく、`padding`や、`font-size`も上記の方法で値を設定してあげると画面幅に合わせて拡大縮小するようになり、画像を埋め込んだ時のような振る舞いをしてくれます。

デザインって画面幅1920pxとかで設計されてたりしますよね？
もう、上の式にそのまんま値を当てはめてCSSを書いていくと、レイアウトが画面幅に合わせて拡大縮小する`絶対に崩れない要素`が書けてしまいます！
下記例では画面幅600pxの時を想定して書いておりますが、どの画面幅でも崩れずに表示されているので確認してみてください！

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="jOZpmrB" data-user="seiyokabure" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/seiyokabure/pen/jOZpmrB">
  絶対に崩れないCSSの書き方</a> by Seiyokabure (<a href="https://codepen.io/seiyokabure">@seiyokabure</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 計算方法どうなってるの？

### VWからおさらい

計算方法について書く前に、まずはvwについておさらいですが、
`vw`はviewport（表示領域）の横幅`100％`を`100vw`として扱います。
要は見ているブラウザの幅を基準にサイズを決めてくれるんですね。

ということは、表示している幅が`1000px`のとき、`100vw`が`1000px`、`10vw`が`100px`となり、`1vw`は`10px`となります。

ですので、

`width: 10vw;`

と指定した場合は要素の横幅は常表示領域の横幅に対して`10％`の大きさで表示してくれます。

ただ、これを使いこなすのには画面幅が常に変わりゆく中で、`vw`をいくつに設定したらいいのか？と言うのが悩みどころです。

### 計算方法

ということで、どの様にvwを計算したらよいかと言うのを書いていきます。

まずは`1vw`あたりの`px`を出したいと思います。

例えば`1000px`で表示している際の`1vw`あたりの`ピクセル数`を出したいとなると`100vw`で割れば良いですね。

`1000px ÷ 100vw = 10px`

となりますので`1vw`で`10px`となります。
これはかんたんですね。

`10vw`のときの`px`を出したければ

`10 × (1000px ÷ 100vw) = 100px` 

`1vw`が`10px`ですからそれの`10倍`ですね。

じゃあ、逆に`100px`のときの`vw`を出したいときは？

`100px ÷ (1000px ÷ 100vw) = 10vw`　です。


となると、`1000px`の表示領域の時、`100px`となる`width`を書きたい場合は
`10vw`が答えです。

ということで計算できたら`width: 10vw;`とCSSに書いてください。
上記の計算を毎回するならば`vw`ですべて完結します。
めんどくさいですね…

### calc()を使いたい

そこで上記のような面倒な計算をしてくれるのがcalc()関数です。
式を書けば自動で算出してくれる便利な関数ですね。
`width: calc(2 + 1);`と書けば`３`になります。

calc()に入れるためにはどうしたらいいかというと、

- 1vwの出し方は100vwで割るだけ、
`(1000px　÷　100vw) = 10px`

- 10vwの出し方は1vwの10倍だから
`(1000px　÷　100vw)　×　10vw = 100px`

- 求めたいのは100px、上記の式を割り算にしたらいいですよね。
`100px　÷　（1000px　÷　100vw）　=　10vw`

- 演算子に変換して単位は一旦取ってしまいましょう。
`100　/　（ 1000　/　100 ）　=　10`

- calc()にいれて、出力したい単位を入れましょう。
 `calc(100vw　/ ( 1000 / 100 ) )`

- 上記の形で完成でも良いのですが、冗長なので自分は割り算をしてしまっています（保守のこと考えてない等、賛否両論あるかもですが、、、）
`calc(100vw/10)`


ということで`1000px`のとき、`100px`となるcalc()の計算は

`calc(100vw/10)`

となります！

## 最後に注意点

コチラは`デザインを忠実に再現したいセクションだけ`というような限定的な使い方に留めたほうが良いです。

### vwはスクロールバーのサイズを含まない…

その理由の一つ、`vw`は厳密には画面幅、ではなく画面右側のスクロールバーも含めて計算しています。
一方で、`width: 100%`と書いている場所はスクロールバーを除いた幅を計算しています。
そして厄介なのがWindowsとMacでスクロールバーの幅が変わります…ユーザーの設定にもよることもあります。

`width: 100%;`で作っているセクションと誤差が出ることを理解し、`width: 100vw;`を適用しているヒーロービューセクションだけとか、誤差が出ない様にCSSを当ててとか、使いこなすのには特性を理解した上で使ってください。

また、全ページこのcalc()関数で書いてしまうと保守する人が自分でない場合はわからなくなってしまいます。
コメントで式を書いといてあげるとか、工夫が必要です。

何はともあれ、`コーディングの段階`で、デレクターさんやデザイナーさんと`共有`し、事前に`相談`しましょう。
とは言いつつも、メディアクエリで段階的に調整するよりは`再現の精度がかなり高いものが作れます!`

### 無限に大きく、無限に小さくなる…

そして最大の弱点は、画面幅に対してフォントサイズなどは無限に大きくなるし、無限に小さく（Chromeは10pxが最小に）なります。

こちらは`max-width`などを使うと良いですが、フォントサイズは無理です。

Sassを使うと`max-font-size`という裏技も使えるので、そのテクニックは改めて別記事で書こうかと思います。
一長一短ある書き方の一つですが、ぜひお試しください！