世界を動かす技術を、日本語で。

無限のピクセル

概要

  • CSSでinfinity値を指定 した場合のブラウザ挙動の検証
  • Safari、Chrome、Firefox での結果比較
  • width, height, font-size, line-height それぞれの制限値や挙動の違い
  • 計算値とレイアウト値の 不一致や謎の制限値
  • さらなる調査の余地 と、読者への情報提供呼びかけ

CSSでinfinity値を指定した際のブラウザ挙動調査

  • Andy Pのtoot で紹介されたCSSトリック「width: calc(infinity * 1px); height: calc(infinity * 1px);」を検証
  • box modelの影響排除 のため、marginとpaddingも0に設定
    • div { width: calc(infinity * 1px); height: calc(infinity * 1px); margin: 0; padding: 0; }
  • テスト環境 :macOS上のFirefox Nightly、Chrome stable、Safari stable

width, heightの挙動

  • Safari: 計算値・レイアウト値ともに33,554,428px
  • Chrome: 計算値・レイアウト値ともに33,554,400px
  • Firefox: 計算値はheightが19.2px(デフォルト行高)、widthは17,895,700px。レイアウト値はheightが19.2px、widthが8,947,840px
    • heightのみinfinity指定がほぼ無視され、デフォルトの行高に
    • widthは計算値とレイアウト値が大きく異なる

制限値の考察

  • Safari/Chrome は約33,554,431(2^25-1)付近で制限
    • 24bitや32bit境界の可能性、もしくは内部実装の都合
  • Firefox は独自の制限や処理
    • heightではinfinityを無視、widthでは計算値とレイアウト値が大きく乖離

font-sizeにinfinityを指定した場合

  • Safari: 計算値・レイアウト値ともに100,000px
  • Chrome: 計算値・レイアウト値ともに10,000px
  • Firefox: 計算値は3.40282e38(32bit floatの最大値)、レイアウト値は2,400px(実際は2,000px+line-height1.2の可能性)
  • line-height: 1; を指定すると、divの高さが8,947,840pxに拡大

font-size制限値の特徴

  • Safari/Chrome は明確な10進数の上限
  • Firefox は計算値と実際の描画値が大きく異なる
  • line-heightの設定 でレイアウト値が大きく変動

line-heightにinfinityを指定した場合

  • Safari: 計算値・レイアウト値ともに33,554,428px
  • Chrome: 計算値・レイアウト値ともに33,554,400px
  • Firefox: 計算値17,895,700px、レイアウト値8,947,840px
  • width指定時とほぼ同じ挙動

考察と今後の課題

  • 各ブラウザごとに異なる制限値や実装方針
  • 計算値とレイアウト値の不一致 が特にFirefoxで顕著
  • infinity指定の値がどこで、なぜ制限されるか は謎が多い
  • さらなる検証や情報提供 を読者に呼びかけ
  • calc(-infinity) など他のパターンの実験余地

まとめ

  • CSSでinfinityを指定した場合のブラウザ挙動 は一様ではなく、各エンジンの実装や歴史的経緯に依存
  • 制限値や処理方法の違い から、限界値テストやデバッグ時の参考情報
  • 今後の調査や知見の共有 が期待される分野

Hackerたちの意見

この記事を読んで、20年前のことを思い出したよ。ブラウザをハックするために、いろんなクレイジーなCSSを使ってたな。今のCSSは(普通は)比較的まともな世界になったね。

面白い時代だったな。IE5で赤いボックスを作るCSSスニペットを書ける? IE6+では青、Firefoxでは黄色、Netscapeでは黒になるやつ。今のCSSはもう冒険じゃなくて、普通に動くからね。

CSSリセットはいつも使ってるよ。それと、紫の話ね。

Blink、つまりChromeのレンダリングエンジンは、2013年にWebKit(Safari)からフォークされたんだ。フォーク当時、WebKitはすでに無限値をサポートしてたから、同じ基盤のコードを共有してる可能性が高いね。これが彼らの似た動作の理由だと思うよ。[0]: https://caniuse.com/?search=infinity

あなたは、ずっと前からあるJavaScriptのキーワード「Infinity」と、ここ2〜3年で登場したCSSのキーワード「infinity」を混同してるよ。実際、ここで「infinity」キーワードが関係あるとは思えないな。calc(infinity * 1px)を9999999999999999pxに変えても、同じ結果になると思う。Firefox(そして、昔のIEも)は、過剰に大きな高さの宣言を無視するし、WebKit系のブラウザはそれを制限するよ。

CSSで「infinity」の実際の使用例って何?

z-indexの最終ボス

Tailwind CSS v4では、ピル型のボーダーに使ってるよ:.rounded-full { border-radius: calc(1px * infinity); } …歴史的にみんながやってきたのは、9999pxみたいな適当な大きい値を選ぶことだけど、実際のメリットはないけど、なんか「正しい」感じがして満足。

規格によると:これらの定数は、無限やNaNの値のシリアライズを簡単にするために主に定義されてるけど、「最大の可能な値」を示すためにも使えるんだ。無限の値は許可された範囲に制限されるからね。これが合理的であることは稀だけど、そういう時には、巨大な数字をスタイルシートに入れるよりも無限を使う方が意図が明確なんだ。

インフィニティドルウェブサイト™を作るために、1ドルでピクセルを売ってる。

Firefoxは、正確に17895697pxを超える値になる高さの宣言を無視するんだ。この値って何かって? ちょうど2³⁰分の1ピクセル未満で、Firefoxのレイアウト単位なんだ。(これは2³⁰分の1ピクセルの直前の整数で、17,895,697.06̅ピクセル、4⁄60多い。)Firefoxは32ビットの符号付き整数を使ってると思うし、別のビットを何かのために予約してるんじゃないかな、たぶんオーバーフロー制御とか。5年前は、FirefoxはそういうCSS宣言を無視してたけど、いつの間にかほとんどのものが制限されるようになって、WebKit系の動作に合わせてる。でも、高さはそうなってないみたいで、ちょっと驚いた(そうなると思ってた)。WebKit系のブラウザは、1⁄64ピクセルのレイアウト単位を使ってるから、そう考えると、2²⁵ − 1ピクセルは実際には2³¹ − 1レイアウト単位で、あまり驚くべき数字じゃないね。IEは、昔のFirefoxと同じ動作をしてたけど、制限がずっと低くて、10,737,418.23ピクセル(2³⁰ − 1百分の1ピクセル)だったから、Fastmailには現実的に問題を引き起こすには十分だった。メールボックスに約200,000件のメッセージがあればよかったからね。これについては何度か書いたことがあるよ。https://news.ycombinator.com/item?id=42347382, https://news.ycombinator.com/item?id=34299569, https://news.ycombinator.com/item?id=32010160。

Firefoxの単位は結構賢いよ。60は3、4、5、6で割り切れるから、将来的にdevicePixelRatioが6のディスプレイが出ても大丈夫だね。

興味がある人のために、WebKitでは、ほとんどの計算された長さの値にLayoutUnit(https://github.com/WebKit/webkit/blob/main/Source/WebCore/pl...)を使ってるんだ。LayoutUnitsは、最小単位が1/64ピクセルの固定小数点表現を使ってる。https://trac.webkit.org/wiki/LayoutUnitはちょっと古いけど、いい情報が載ってるよ。

この2^25-1ピクセルの制限は完璧に納得できるね。1/64ピクセルの精度だと、ちょうど2^31-1レイアウトユニット(符号付き32ビット整数の最大値)になる。

ほぼ24ビットの制限って、32ビット浮動小数点数が24ビットの有効数字を持ってることと関係あるのかな?(参考:https://en.wikipedia.org/wiki/Single-precision_floating-poin...)

正しい答えだね。特に、2^24まではfloat32は普通の整数みたいに振る舞うから、場合によっては重要なんだ。そこを超えると、整数に欠損値が出始めて、(n+1)+1が(n+2)と等しくないみたいな変な挙動が出てくる。

でも、具体的には24ビットじゃなくて25ビットなの?2^24を超えるとn+1がnと等しくなるから、ここでは2^25-1にいるわけだし。だから、理由としてはちょっと納得がいかないな。上にある投稿では、ブラウザがピクセルを1/64単位に分けてるから、6ビットを考慮すると、この制限はちょうど符号付き32ビット整数の制限と一致するんだ。浮動小数点数の有効数字よりも、こっちの方がずっと理にかなってる。

いや、みんなレイアウトには固定小数点数を使ってるよ。詳しくは俺のコメント見て。SVGの話だけど、浮動小数点の精度が落ちるところでビュー ボックスを定義してみて、面白いことが起こるから。

ChromeとSafariはどちらも225-1(33,554,431)に非常に近いけど、Safariはそこから3ピクセルだけ引いて、Firefoxは31ピクセル引いてる。タイプミスだね、この文の最後のブラウザは「Chrome」だよね?

ここで他のコメントでも指摘されてるけど [0]、Glide Data Grid [1] や TanStack Virtual [2] のような仮想DOM/キャンバスベースの「無限」データグリッドを使うと、ネイティブのスクロールバーと同じパフォーマンスや使いやすさが得られるよ。これらのライブラリは、内部で非常に大きな高さのスクロール可能なDIVを作成してるからね。つまり、大きな空のDIVをスクロールしてて、「無限」グリッドのビューポートは実際にはキャンバスに描画されてるんだ。でも、すごく大きなグリッドになると、この記事で説明されてる高さの制限に近づくと崩れちゃうんだ。俺がやってるプロジェクトでは、結局スクロールバーを再実装することになったけど、すごく不安定で、特にモバイルでは「フリック」や慣性のタッチジェスチャーが失われちゃうからね(それを再実装するのはリスクがあるし)。これを解決するための良いトリックやライブラリってある?ありがとう! [0] https://news.ycombinator.com/item?id=44825028 [1] https://github.com/glideapps/glide-data-grid [2] https://tanstack.com/virtual/latest

一つのスクロールジェスチャーが閾値を超えることはなさそうだし、スクロールバーのサムも見えないかもしれない(意図的にか、極端な高さのせいで)。だから、「無限スクロール」のページネーションされた仮想リストのスタックがあれば十分かも?要するに、各「アイテム」(仮想リスト自体)の終わり/始まりに達したらメインコンテナを入れ替えるだけの単純な「もっと読み込む」実装ね。それが役に立たないなら、これ面白い投稿をチェックしてみて(ネイティブスクロール体験なし):https://everyuuid.com https://eieio.games/blog/writing-down-every-uuid/

ちょっと関係あるけど、Netscapeは読み込み中のレイアウトが無限だと仮定して、サイズがわかるとそのサイズに機能を解決してたんだ。だから、通常は全てが読み込まれるまで何も表示されなかった。IE4は逆で、全てのサイズがゼロだと仮定して、サイズがわかるとそれを埋めていった。これのおかげで、オブジェクトを読み込んでページオブジェクトが読み込まれるときに表示されるのが、Netscapeよりもずっと速いように見えたんだ。こういう初期のエンジニアリングの決定が会社を成功させたり失敗させたりすることがあるよね。Netscapeが直面した唯一の課題ではないけど、Geckoレイアウトエンジンを作る必要を本当に促進した要因の一つだった。マイクロソフトのエンジニアが内部で何が起こったかを知り得ないのに書いた、非常に誤解を招く有名なブログがあったけど、それはもう完全にひっくり返って自己中心的だったし…。

もし24ビットの値が流行ってた時期があったなら、俺は見逃してたな。単精度のIEEE浮動小数点の仮数は23ビットだよ。正規化された浮動小数点の隠れビットを数えると、24ビットになる。