CSS で計算(四則計算)が行える calc() 関数を使ってみよう

CSS プロパティに指定する値を計算式で書くことができる仕様、calc() 関数について、具体的なサンプルソースをそえて解説してみたいと思います。

CSS の calc() 関数は、CSS プロパティに指定する値を計算式で書くことができる仕様です。

ちょっと前に Firefox 48 が、この calc() 関数の入れ子表記に対応した件で下記のような記事を書いたのですが、その時参考にリンクを張ろうとした、過去に calc() 関数についての解説を書いた記事がもう6年も前の記事で、さすがに古すぎるということでリンクを張れなかったんですが、いい機会なのでちょっと書き直してみます。

なお、上記記事でも書いたとおり、calc() 関数は現時点で IE (IE11)や Edge を始め、Firefox、Chrome、Safari など、ほぼすべてのモダンブラウザがサポートしていますので、念のため古いブラウザ向けに最低限のフォールバックとなる記述だけしておいてあげれば問題なく使用できます。

calc() 関数の基本的な記述方法

calc() 関数の仕様は下記にあります。

使用できる演算子は四則計算、加算(+)、減算(-)、乗算(*)、除算(/)で、<length>(長さ)、<frequency>(周波数)、<angle>(角度)、<time>(時間)、<percentage>(%)、<number>(数値)、<integer>(整数) 型の値が許容される場所であればどこでも使用できます。

逆に言えばそれ以外の場所では使えませんので、例えば color プロパティの値として使用したりはできません。

基本的な記述方法は下記のようになります。

section {
  width: calc(100%/3 - 2*1em - 2*1px);
}

この時、加算と減算 (+, -) に関しては演算子の前後に半角スペースを必ず入れなければなりません。乗算と除算 (*, /) に関しては前後の半角スペースについて任意ですが、ミスを防ぐためにも記述上は前後に半角スペースを入れる記述で統一しておいた方がよいでしょう。

書式についてもう少し詳しく書いていきますが、仕様書上は下記のように書かれています。

<calc()> = calc( <calc-sum> )
<calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
<calc-product> = <calc-value> [ '*' <calc-value> | '/' <number> ]*
<calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )

また、あわせて下記のように定められています。演算子ごとに使用できるオペランド(演算の対象となる値)が決まっていますので覚えておく必要があります。

  • At + or -, check that both sides have the same type, or that one side is a <number> and the other is an <integer>. If both sides are the same type, resolve to that type. If one side is a <number> and the other is an <integer>, resolve to <number>.
  • At *, check that at least one side is <number>. If both sides are <integer>, resolve to <integer>. Otherwise, resolve to the type of the other side.
  • At /, check that the right side is <number>. If the left side is <integer>, resolve to <number>. Otherwise, resolve to the type of the left side.

わかりやすく日本語で説明しておきますと、

演算子が + または - の場合

演算子が + または - の場合、「演算子両側の値が同じ型」、もしくは 「一方が <number> (数値) で、もう一方が <integer> (整数)」であるかがチェックされます。前者の場合はその型に解決され、後者の場合は <number> に解決されます。

ちょっとわかりにくいかもしれませんが、要するに width: calc(100% - 10px)width: calc(10rem + 10px) は演算子の両側が <length> (<percentage> は <length> ではないのですが、width プロパティは <length>、<percentage> を許容し、いずれも長さとして扱われるため同じ型となります) になるので、正しい記述ですが、width: calc(100px + 10s) のような記述は「長さ」 と 「時間」 という異なる型の値が指定されていますので、不正ということになります。

演算子が * の場合

「演算子両側の値のうち、少なくともどちらか一方が <number>」 であるかがチェックされます。両側とも <integer> であれば <integer> として解決され、それ以外であれば、<number> と逆側に指定された値の型で解決されます。

これも上記だけだとわかりにくいので補足すると、calc(10px * 10px) のような指定は不正です。calc(10% * 2) または calc(2 * 10px) のようにしなければなりません。

演算子が / の場合

「演算子の右側が <number>」 であるかがチェックされます。演算子の左側が <integer> であれば <number> として解決され、それ以外であれば、左側の値の型に解決されます。

要するに <number> で割らないといけないということですね。calc(2 / 10px) のような記述をしてはいけません。calc(10px / 2) のようにしましょう。また 「0」 での除算はエラーになります。

計算の優先順位

calc() 関数による四則計算の優先順位は学校で習うとおりです。計算順序を指定するために 「括弧()」 を使用することができます。

calc(500 - 10 * 20 - 10 / 2) となっていれば、500 - 200 - 5 ということになりますね。例えば加算、減算を優先的に計算したければ calc((500 - 10) * (20 - 10) / 2) のように記述する必要があります。

応用編: 具体的なサンプルソース

基本的なところがわかったところで、具体的にどういうところで使うケースが考えられるか、サンプルソースを挙げてみます。

実用例 1: line-height とあわせて padding を計算したい

例えば、デザインで下記のようにして欲しいという指定があったとします。わかりやすいようにフォントサイズは px 指定にしていますが、上下の余白を 20px とってねという指定です。

デザイン例: 文字サイズは 36px 指定、文字の上下に 20px ずつの余白がつく前提のデザイン

で、例えば下記のように CSS を指定しますが、

h1 {
  font-size: 36px;
  line-height: 1.4;
  color: #fff;
  background-color: #f00;
  padding: 20px;
}

実際にどうなるかというと、line-height の指定があるので、下記のように表示されるはずです。

実際の表示例: 実際には line-height に対して上下に 20px の余白がつくため、デザイナーの意図とは余白のサイズが違ってしまう場合も

文字の上下に (font-size * line-height - font-size) / 2、つまり上の CSS の例で言えば、(36px * 1.4 - 36px) / 2 = 7.2px ずつスペースができますので、見た目上、ぴったり 20px の余白にはならないということになります。

で、無茶な方法としては line-height: 1; とかしちゃう手もありますが、テキストが折り返された時に非常に可読性が悪く、よろしくない感じになりますので避けたい。じゃあどうするかで、下記のように calc() 関数の出番です。

h1 {
  font-size: 36px;
  line-height: 1.4;
  color: #fff;
  background-color: #f00;
  padding: 20px; /* 旧ブラウザ向けに念のためフォールバックとして残す */
  padding: calc(20px - (1em * 1.4 - 1em) / 2) 20px; /* calc() を使用した記述を追加 */
}

別に同じ見出しに対して font-size: 1.5rem; みたいな指定がされている場合でもこの計算式で同じ効果が出せますね。

line-height については値が変わったら、計算式の中の 1.4 を変えないといけません。この辺はカスタムプロパティとの併用が可能な環境であればもっと楽に書けそうです。

実用例 2: 背景画像を常に左右中央から 10px だけ左に配置したい

background-position: center top; とか、background-position: 50% 0; とか指定すれば、x軸(横)方向については画面中央に背景画像が表示されると思いますが、この背景画像を 10px だけ左にずらしたい場合、下記のように指定すれば実現できます。

.sample {
  background-image: url(sample.jpg);
  background-repeat: no-repeat;
  background-position: calc(50% - 10px) 0;
}

calc() 関数に対応しない環境用に、念のため下記のようにフォールバックとなる記述をしておくとよいかもしれません。

.sample {
  background-image: url(sample.jpg);
  background-repeat: no-repeat;
  background-position: 50% 0; /* フォールバック */
  background-position: calc(50% - 10px) 0;
}

実用例 3: background-size の指定をひと工夫

同じく、背景画像関連ですが、例えばサイズが可変する要素に対して1枚の背景画像を表示しているような状況で、必ず左右に 10px ずつ余白を持たせたいなんて場合、下記のように指定することもできます。

.sample {
  background-image: url(sample.jpg);
  background-repeat: no-repeat;
  background-position: 10px 0;
  background-size: calc(100% - 20px) auto;
}

実用例 4: 固定サイズブロックと可変サイズブロック横並び

float プロパティで要素を横並びにしたい場合、例えば左側は画像だから横サイズが固定だけど、右側はテキストエリアで、ここの横サイズは可変にしたいなんてことありますよね。

<div class="flexible-width">
  <!-- 親要素は横幅可変 -->
  <div class="fix-width-img">
    <p><img></p> <!-- このブロックは左側において横幅固定 -->
  </div>
  <div class="flexible-width-text">
    <p>text</p> <!-- このブロックは右側において横幅可変 -->
  </div>
</div>

でも実際には float プロパティではこれが再現できないので(左右が逆なら float でなんとかできますが)、多くの場合、下記のような指定で頑張ることが多いんじゃないでしょうか。

.flexible-width {
  position: relative;
}
.fix-width-img {
  position: absolute;
  left: 0;
  top: 0;
  width: 200px;
}
.flexible-width-text {
  padding-left: 220px;
}

実際にはテキスト量が少ない時を見越して、画像のサイズがある程度固定なら .fix-width-img に対して、min-height: 200px; みたいな指定をして、高さを確保したりしますが、画像サイズやテキスト量の変化に柔軟に対応しづらいという問題があります。

こういう時も calc() 関数を使えば下記のように楽に書けます。

.flexible-width {
  overflow: hidden;
}
.fix-width-img {
  float: left;
  width: 200px;
}
.flexible-width-text {
  float: right;
  width: calc(100% - 220px);
}

まぁ、今なら CSS Flexible Box (Flexbox) を使う方がよいとは思いますが、こういう使い方もできますよというサンプルのひとつとして挙げてみました。


ということで、calc() 関数はうまく使うととても便利ですので、気になった方はぜひ上の解説を参考に使ってみてください。

関連エントリー

記事をここまで御覧頂きありがとうございます。
この記事が気に入ったらサポートしてみませんか?