目次

CSSのcalc()が単位のない0を計算できないのは仕様

レスポンシブデザインや複雑なUIを実装する時に重宝するCSSのcalc()ですが、calc()では単位のない0は計算できません。

具体的には次のようなコードです。

.hoge1 {
  width: calc(100px + 0);
}
.hoge2 {
  width: calc(100vw - 0);
}

calc()は、calc(100px + 100vw)のように異なる単位の値も計算できるので、上記のコードでも問題なく計算できそうに見えますが、Google Chromeなどのブラウザで実際に見てみると、どちらも計算されたwidth100px100vwではなく0になってしまいます。

これは、calc()は0に限らず、単位のない値を計算することができない仕様だからです。なので、0px0%のように単位をつければ正常に計算できます。

.hoge1 {
  width: calc(100px + 0px);
}
.hoge2 {
  width: calc(100vw - 0%);
}

そもそも、calc()で0を計算させるシチュエーションなんてないだろうと思う方もいるかもしれませんが、calc()とCSS変数を併用する時に式中に0が登場することがあります。

calc()とCSS変数を併用するときの注意

CSSプロパティの値でCSS変数を参照するときはvar(--hoge-height)のように記述します。そして、参照したいCSS変数が存在しなかったときのために、var(--hoge-height, 40px)のように第2引数でデフォルトの値を指定することもできます。

レアケースかもしれませんが、私は特定の環境に依存したCSS変数をcalc()の中で扱う際に、デフォルト値に0を使いたいケースがありました。この時も先ほどの例と同じく、単位を省略せずに0pxのように記述する必要があります。

/* Bad */
.hoge1 {
  width: calc(100px + var(--hoge-height, 0));
}

/* Good */
.hoge2 {
  width: calc(100px + var(--hoge-height, 0px));
}

わかってしまえば単純なので万事解決かと思いきや、stylelintのlength-zero-no-unitを有効にしていると、上記コードのGoodパターンでもリントエラーになってしまい、少しややこしいことになります。

stylelintのlength-zero-no-unitはcalc()の中を無視できない

stylelintのlength-zero-no-unitとは、値が0の時に単位の省略を強制するルールです。stylelint公式のconfigであるstylelint-config-standardでも有効になっていることもあり、多くの人が有効にしているのではないかと思います。

CSSの仕様としてはcalc()の中では0の単位を省略しないのが正しい書き方です。ただし、length-zero-no-unitcalc()の中だけ無効化することはできず、正しい書き方をしても次のようにリントエラーが検出されます。

.hoge {
  width: calc(50px + var(--hoge-height, 0px));
}

style.css
 2:42  ✖  Unexpected unit   length-zero-no-unit

length-zero-no-unitのオプションでcalc()の中だけ無視するようなoptionは無く、stylelint側でこの問題が認知されているのか調べました。既に議論されており、基本的にはfunction-calc-no-invalidというcalc()の式で扱えない記述を禁止するルールの方でエラーにならないよう対応されているのですが、var()に渡している値の中では正常に動作させることが難しいようです。そして、stylelintはあくまでも構文を統一するためのツールであり、CSSの仕様に最適化するツールではないため、 stylelint-csstree-validator というpluginを使って欲しいとのことでした。(解釈間違ってたらすみません。)

Fix false positives for inside calc function in to length-zero-no-unit · Issue #3037 · stylelint/stylelint

ただし、私が実際にこの問題に直面したときはこのpluginまでたどり着いていなかったこともあり、このpluginは使用しませんでした。(今もまだ使ったことはないのでそのうち試してみたい。)

stylelint-csstree-validatorを使わずにエラーを発生させないようにするためには、configやコメントアウトなどでlength-zero-no-unitを無効化しないといけません。

ですが私はcalc()以外では0の単位を省略したいし、いちいちコメントアウトするのはめんどくさかったので、他の解決方法を探しました。この問題に直面したプロジェクトではCSSプリプロセッサーにSassを採用していたので、次のようにInterpolationで解決しました。

.hoge {
  width: calc(50px + var(--hoge-height, #{"0px"}));
}

毎度コメントアウトするのに比べるとめんどくさくないし、そこまでハックなこともしてないので、ちょうどよい落とし所かな思っています。他のCSSプリプロセッサーはあまり触ったことがないのでわかりませんが、きっと同じようなことはできるだろうと思います。

今回は使いませんでしたが、そのうちstylelint-csstree-validatorも試してみようと思います。