🧮

CSS: calc() 関数の infinity や -infinity を使用した数式はゼロ除算で短く書ける

に公開

こなさんみんばんわ。
知っておくといつかは役に立つ(かもしれない)CSS の小ネタコーナーです。

本日は calc() 関数などの中で使えるキーワード infinity-infinity について、軽い話題を書いてみます。

infinity とか -infinity ってなに?

いずれも pie と同じく CSS 数学関数で使える <calc-keyword> 型の定数です。その名のとおり、正方向の無限大(∞)、負方向の無限大(-∞)を表します。この他に NaN(数値ではない)が仕様 (CSS Values and Units Module Level 4) では定義されています。

https://drafts.csswg.org/css-values/#typedef-calc-keyword

単位を持たない <number> 型の定数として扱われるので、CSS で使う際には各プロパティが求める値に合わせて、例えば <length> にするなら calc(infinity * 1px) のようにする必要があります。[1]

実際に CSS で使った時には「可能な限りの最大値」となる

もっとも infinity本当に無限大を表す定数として扱われるのはあくまでも calc() 関数の中だけで、実際に CSS の各プロパティに infinity を使った計算式を適用すると、その算出値としては現実的な中で取りうる可能な限りの最大値が与えられることとなります。

例えばある要素を最初はオフスクリーンにしておくために CSS で次のようなスタイルを初期状態で与えておくとします。その状態で開発者ツールを使って要素の left プロパティを確認すると、その値は (-infinity)px などではなく、もっと具体的なピクセル数値を示しているはずです。

.offscreen-element {
    position: absolute;
    left: calc(-infinity * 1px);
}

ちなみに実際に適用される値は、当然ながら環境に依存します。手元のブラウザ(すべて macOS 版)で試してみたところ、次のような値が得られました:

Chrome Firefox Safari
-1.67772e+07px -1.78957e+7px -33554430px

ところでそんなの何に使うんですか?

あまり使う機会は多くないですが、「とても大きな値」を示すためにハードコーティングされた特定の数値、いわゆるマジックナンバーを指定していたような場面は、この infinity を使った計算式で置き換えることができます。

例えば(あまり良くない例ですが[2])その昔流行った画像置換というテクニックは text-indent でテキストを画面外に飛ばして背景画像に指定したロゴなどを表示するというものでしたが、この場合によく使われていた印象のある -9999px という数字などはまさにそういったマジックナンバーのひとつといえます。

もう少し有用な例でいえば、カプセルのような形のボタンを CSS でデザインする場合。例えばまさにこの Zenn で記事を書く画面にある右上のボタン類がそうですね👇

Zenn で記事の公開・非公開を切り替えるスイッチと、下書き保存や公開・更新保存をするためのボタン。左右の丸みは border-radius プロパティを使って実現している

これは border-radius プロパティに要素の大きさ以上の値を指定すると、その値は要素の短辺の 50% に調整されるという特性を活かして実装されているのですが、どんな大きさの要素でもひとつの CSS で対応しようと思ったら、あらかじめ border-radius に非常に大きな値を指定しておかないといけません。そんな場合にこの計算式が利用できますし、またコードを読んだ人にも「ああ、ここは無限大の値を指定したいんだな」というのが明確に伝わる…ような気がします。涙

https://zenn.dev/helloiamktn/articles/f03c6d950e7a0a

ちなみにこれ余談なんですけど、Zenn のこのボタンの border-radius 値は 99rem で指定されていました。このままだと幅高さ共に 199rem 程度以上のボタンが来たら壊れる計算ですね。中の人は気をつけた方がいいでしょう。

そんなボタンないわ😇

calc() の中でゼロ除算をすると infinity が返ってくる

ところで、calc() 関数内でゼロでの割り算をするとどうなるでしょう? 普通の電卓や一部のプログラミング言語だとエラーで怒られるところですが、calc() 内の引数のシリアライズは IEEE-754 標準に従うとされておりますので、この場合は割られる側の数値によって infinity もしくは -infinity を返します。

Console
const d = document.createElement('div')
d.style.scale = 'calc(1 / 0)'
d.style.scale // returns 'calc(infinity)'
d.style.scale = 'calc(-1 / 0)'
d.style.scale // returns 'calc(-infinity)'

なので、先のコードでいうと calc(-infinity * 1px)calc(-1px / 0) と書き換えることができます

書き換えて何か得することある?

7 文字も短く書けます。以上です😅
あとは万が一 infinity の綴りをど忘れしても大丈夫、とか🤣

まぁ実際のところ利点としてはそれくらいですね。とてもトリッキーな書き方なのは間違いないですし、数式内から infinity というキーワードが取り除かれる結果、コードを読んだ人にその意図が伝わりにくくなるという可能性はなきにしもあらず…

なので、複数人のチームで共同開発しているような現場で使用するのは、ちょっと避けておいた方がいいかもしれないですね。でも個人開発のプロジェクトだとか、自分のサイトを自分だけで運用してるとかいう場合には、自分だけが理屈を分かっていればいいので、割と便利に使えるんじゃないでしょうか。

そんな訳で

calc(infinity * 1px)calc(1px / 0) で置き換えられるけどご利用は計画的に、という小ネタでした。

脚注
  1. 掛け合わせる値は別に何でもいい(calc(infinity * 1em)calc(infinity * 100px) なども有効といえば有効)のですが、結局最終的にブラウザが算出する値は無限大のピクセル値に収束するので、そんなことをする必然性ははっきり言ってないです。 ↩︎

  2. なぜ「あまり良くない」かはその昔ブログに書いたことがあります。この当時でもやや昔話だったので今となっては相当な昔話ですが、時間があるようでしたらお読みいただけますと幸いです → 「画像置換はアクセシブル」じゃないよ、という話 / JeffreyFrancesco.org ↩︎

Discussion