CSS の calc()
関数は、サイズや形状を決定する値の指定を計算式で書くことができる仕様で、現時点では IE (IE11)や Edge を始め、Firefox、Chrome、Safari など、ほぼすべてのモダンブラウザがサポートしています。
ちょっとした計算を簡単に書けるので私もよく使用する記述なのですが、現在、Beta リリースの Firefox 48 は、この calc()
関数の入れ子記述に対応しました。後述しますが、CSS Variables (CSS カスタムプロパティ) と組み合わせるときにかなり便利になりました。
calc()
関数の基本的な記述
例えば簡単なところだと、絶対配置したある要素の横幅を画面サイズに対して可変にしつつも、左右に余白を 10px ずつ持たせたいという場合。
.sample { position: absolute; left: 10px; width: calc(100% - 20px); }
のような書き方ができますし、下記のような記述をすれば、
body { font-size: calc(100vw / 20); }
Viewport (ビューポート) の幅に対して、概ね 1行が 20 文字になるようにフォントサイズが計算されたりします。まぁ上の例くらい単純なやつだと最初から font-size: 5vw;
って書けばいいじゃんと言われてしまえばそうなんですけども。
うまく使うと background-position
プロパティの値や position
プロパティで配置した要素の幅や高さの指定、あるいは疑似要素の位置指定、あとは margin
の指定時などに便利かもしれません。
calc()
関数に関する詳しい説明は 「CSS で計算(四則計算)が行える calc() 関数を使ってみよう」 を参考にしてみてください。
入れ子にできるとどうなるの?
さて本題です。calc()
関数を入れ子にできるとどうなるかというと、例えば下記のように書けるようになります。
.sample { width: calc(3 * calc(20px + 30px)) }
上の例だと入れ子にしなくても calc(3 * (20px + 30px))
と書けばよいのであまり意味がないのですが、この calc()
関数を入れ子にできることが活きてくるシチュエーションがあります。
CSS カスタムプロパティとの組み合わせで本領発揮
CSS Variables (CSS カスタムプロパティ) とは、CSS でカスタムプロパティを定義し、変数として使用できるようにするための新しい CSS 仕様です。このカスタムプロパティは、calc()
関数が入れ子にできることで、非常に便利な書き方ができるようになります。
ちなみに、CSS カスタムプロパティは Firefox、Chrome などはサポート済みですが、IE、Edge が未サポート (参考) ですので、お仕事などではまだちょっと使いにくいのが難点です。
で、具体的な使い方に話を戻しますが、例えば下記のような HTML があったとして、これを Flexbox でレイアウトする場合を考えてみます。
<div class="sample"> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <div>6</div> <div>7</div> </div>
Flexbox に関する詳しい説明は 「CSS Flexible Box (Flexbox) を使ってみよう - 最新仕様対応版」 を参考にしてみてください。
CSS を書くと例えば下記のような感じになりますね。これで横並びのカラムができあがります。
.sample { display: flex; flex-wrap: wrap; } .sample div { flex: 1 1 auto; }
もし、各カラムの最小幅を指定したければ、
.sample { display: flex; flex-wrap: wrap; } .sample div { flex: 1 1 200px; }
のようにすればあとは各カラムの幅が 200px
になるべく近づくようにユーザエージェントがよきに計らってくれます。
さて、このとき画面サイズにかかわらず、常に 5つのカラムが横並びになるようにしたければ、.sample div
に対する flex: 1 1 200px;
という指定の 200px
という値を調整すればよいということになりますね。
親要素 (.sample
) のサイズが画面サイズに対して可変の場合、ここの値を 20%
などとしてあげればよいですが、同じことを calc()
関数によって書くこともできます。
この場合、下記のように指定してあげれば、親要素の幅に対して、常に 1/5 の数値が入りますので、必ず 5カラムの横並びになります。
.sample div { flex: 1 1 calc(100% / 5); }
もちろんこの指定だけだとどんなに画面が小さくても 5カラムにしようとするのでおかしいことになりますが、その辺は説明を簡易にするため今回は無視しています。
まだ利便性がイマイチ伝わっていないですね。そこで次にいよいよ CSS カスタムプロパティを使った場合を考えてみます。
例えば親要素の幅を calc()
関数を使って書いた上で、上記したカラム数も calc()
関数によって指定する方法を組み合わせるとします。
CSS カスタムプロパティに関する詳しい説明は 「CSS Variables (CSS カスタムプロパティ) の使い方」 もあわせて参考にしてみてください。
まず、親要素の幅の指定に使う指定と、カラム数をカスタムプロパティで定義しておきます。
:root { --wrap-box-width: calc(100% - 10px * 2); --box-column: 5; }
で、こいつを下記のように呼び出そうとしますね。
.sample { display: flex; flex-wrap: wrap; width: var(--wrap-box-width); margin: 0 auto; } .sample div { flex: 1 1 calc(var(--wrap-box-width) / var(--box-column)); }
一見これで問題なさそうですが、展開してみると .sample div
に対する指定は下記のように解釈されるので......
flex: 1 1 calc(calc(100% - 10px * 2) / 5);
calc()
関数が入れ子にできない環境では未知の値になってしまい正しく動作しません。入れ子にできる環境であれば正しく解釈されますので、こういう複雑な指定も簡単に書くことができます。
もちろん、記述の仕方を工夫することで calc()
関数が入れ子にならずに同じ記述をすることもできます。例えば下記のように書けば結果は同じです。
:root { --wrap-box-width: 100%; --wrap-box-margin: 10px; --box-column: 5; --box-item-width: calc((var(--wrap-box-width) - var(--wrap-box-margin) * 2) / var(--box-column)); } .sample { display: flex; flex-wrap: wrap; width: calc(var(--wrap-box-width) - var(--wrap-box-margin) * 2); margin: 0 auto; } .sample div { flex: 1 1 var(--box-item-width); }
ですが、ちょっと煩雑になってしまいましたね。
で、カスタムプロパティを使用すると何が便利かというと、最初に各値を定義しておけば、あとで好きなときにそれを呼び出せますし、例えば先ほどのカラム数でいうと、5カラムで並べていたのをやっぱり 4カラム横並びに変えたいな...... なんてときにも 1ヶ所変更すれば済んでしまいます。もしメディアクエリで画面サイズごとに色々書いていたりしても楽ですね。
さらに、calc()
関数を組み合わせることで様々な条件に合わせた指定が比較的簡単に書けてしまうので、この 2つの組み合わせはかなり強力です。
もしかすると 「Sass 使ってるので CSS ネイティブな演算やカスタムプロパティ (変数) なんかいらないよ」 という人もいるかもしれませんが、CSS カスタムプロパティは動的に変数をセットしていけますし、calc()
関数による演算もそれをそのまま扱えるので Sass などにはない利点があります。うまく使い分けるととても便利だと思いますよ。
ということで、この calc()
関数の入れ子に対応した Firefox 48 はまだ現時点で Beta リリースのため、正式リリースされるのはまだ少し先の今年 8月頭 (予定)ですが、この辺のサポートが進むと CSS を書く際にとても便利なので他のブラウザも追随して欲しいなと思います。