min()やmax()、clamp()関数での単位なしの0

ようやくmin()max()clamp()関数が、安定して使えるように揃った。で、いろいろ試していたんだけど、値に<length-percentage>を要求するプロパティーで使う時、その中で0を使うことができなくてハマった。なので0pxや、短くしたいなら0%0Qと書かないといけない。calc()関数での制限と同じ。

具体的には以下のように書くと動かない。よくmargin-left: 0と書くのでハマりやすそう。

.test {
  margin-left: clamp(
    0,
    (100vw - 37rem - 1em) / 2,
    5vw
  );
}

これらの関数のカンマで区切られた値は、それぞれ数式として処理される。そのため、calc()関数と同じように、0は計算結果として0を返すので、<integer>を受け入れるプロパティーにのみしか使えない。仕様でも「Additionally, math functions that resolve to <number> can be used in any place that only accepts <integer>.」と触れられている。margin-leftプロパティーの値は<length-percentage> | autoなので、3行目の0でエラーになってしまう。

0%と書けば、問題なく動く。calc(0 - 20rem)などと、0から引いて負の値を作ろうとしてエラーになるのと似ているけど、ちょっと違う。こっちは両辺の型が一致していないので、計算式として成立しないというエラー。


上記例のclamp()関数は、幅37remの要素をほぼセンタリングしつつ、大きい画面では5vwの余白で左に寄せようというもの。普通、センタリングするにはautoキーワードを使えばいいけど、clamp()関数内ではautoキーワードが使えないので、計算しなきゃならない。5行目の最大値だけでなく、3行目の最低値が必要なのは、描画領域の幅が37rem以下だった場合に、負の値になってしまうから。

こういうものは、通常、メディア・クエリーでやる。この場合は多分こう書くだろう。

.test {
  margin-left: auto;
}

@media (min-width: 41.111em) {
  .test {
    margin-left: 5vw;
  }
}

37remをセンタリングした時に、左の余白が5vw以上にならないようにしたいので、37 / 0.9 = 41.111がメディア・クエリーのmin-widthで指定する値になる。これで期待通りに動作する。

この例ではclamp()関数で書いた方が短くなっているので、CSSの軽量化にもつながる……と言いたいところだけど、ならない。サポート状況の前後を考慮すると、だいたいclamp()関数内でカスタム・プロパティーを使うだろうと考えられる、かなり長くなる。


スコープ化できるカスタム・プロパティーを利用できるため、引数の解釈が初期状態で固定されるメディア・クエリーよりも、柔軟に対応できる。対して、calc()関数と同じように単位付きの値から単位なしの値を計算できないので、やれることにも限界がある。いろいろな知識を増やしたうえで、どう使うのが正しいのか、どうメディア・クエリーと使い分けるのか、これらの関数にしかできないことは何かなどを見極めなければならない。

その前に、最小値を設定したいならmax()関数で、最大値を設定したいならmin()関数だという、max-widthプロパティーと逆なことをおぼえないといけない。間違えまくった。


min()max()関数は、引数がひとつだけの場合(min(1.618 * 1rem)とか)、calc()と等価になる。CSSの最小化ツールにcalc()min()に置き換える機能があっても良さそう。数年後くらいには。