CSSのcalc()は単位のない0を計算できない
CSSのcalc()が単位のない0を計算できないのは仕様
レスポンシブデザインや複雑なUIを実装する時に重宝するCSSのcalc()
ですが、calc()
では単位のない0は計算できません。
具体的には次のようなコードです。
.hoge1 {
width: calc(100px + 0);
}
.hoge2 {
width: calc(100vw - 0);
}
calc()
は、calc(100px + 100vw)
のように異なる単位の値も計算できるので、上記のコードでも問題なく計算できそうに見えますが、Google Chromeなどのブラウザで実際に見てみると、どちらも計算されたwidth
は100px
や100vw
ではなく0
になってしまいます。
これは、calc()
は0に限らず、単位のない値を計算することができない仕様だからです。なので、0px
や0%
のように単位をつければ正常に計算できます。
.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-unit
はcalc()
の中だけ無効化することはできず、正しい書き方をしても次のようにリントエラーが検出されます。
.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を使って欲しいとのことでした。(解釈間違ってたらすみません。)
ただし、私が実際にこの問題に直面したときはこの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も試してみようと思います。