tan(atan2()): 把长度转换成 number
calc() 不许「长度除长度」——calc(100vw / 1px) 直接 parse error,于是「100vw 到底是多少 px」这个数字在 CSS 里一直无法取得。Jane Ori 发现的技巧:atan2(A, B) 接受两个同量纲的值(普通 atan() 只接受
number,这正是入口),内部只取比值、单位随即约掉,输出一个角;tan() 再把角还原成比值——tan(atan2(A, B)) = A / B,一对三角函数充当单位消除器。
1 · 原理:一对三角函数当除法器
为什么单位约得掉: atan2(y, x) 的类型规则是「两个参数同类型即可」——它需要的只是 y 与 x 的比值和各自的符号(相比 atan(y/x),多出的能力是按符号区分象限)。比值是纯数,所以
这种 calc 写不出的除法,在 atan2 内部是合法的。角再经过一次 tan(),比值就还原出来。负数同样安全:A 为负 → θ 为负 → tan(θ) 为负,符号原样保留。
2 · 实战:100vw 读成数字——@property 先定形
直接写 tan(atan2(100vw, 1px)) 在旧版 Firefox / Safari 上会得到 0(混单位问题,见页尾实测表)。文章给出的稳妥做法:先用 @property 把自定义属性注册成 <length>,赋 100vw 后它在 computed-value time 就被定形为绝对 px,再进 atan2 就是 px ÷
px 的同单位除法。下面框里的数字是纯 CSS 写出来的(counter-reset 收 number、counter() 打印),拉伸窗口试试:
3 · 实战:这个容器有几 em 宽
两个操作数都是相对单位也行:100cqi(容器 inline 尺寸)÷ 1em(字号)=「容器宽折合多少个 em」——排版上很有用的一个数(一行大约能放下 宽÷2 个汉字)。拖两个滑块,看比值怎么跟着变:
4 · 实战:时间也能转——12s 是多少 ms
此方法对任何带单位的类型都成立,不只长度。文章的收尾示例:tan(atan2(12s + 1ms, 1ms)) = 12001——时间被换算成「折合多少个 1ms」。除数选哪个单位,就是换算到哪个单位:
5 · 兼容性问题:实时检测你的浏览器
这一手法毕竟利用了类型系统的边界,各家实现的历史问题不少:文章发表时 (2023) Firefox 混单位返回 0、Safari 不包 calc() 就返回 0。下表把各种写法在你当前的浏览器里逐条实际计算(探针 = 注册成 <number> 的自定义属性 +
getComputedStyle 读回):
两个边界: 除数为 0: atan2(A, 0) = 90deg, tan(90deg) 按规范是 ∞(calc 里合法的无穷,用到属性上再被各自的范围夹住);精度:比值越大 θ 越贴近 90°,浮点误差被 tan 急剧放大——文章的对策是把除数加大 (10000px) 让 θ 离开危险区,算完再乘回
10000。两条在上表里都有实测行。
2026 年还需要此方法吗? 视场景而定:「归一化到 0–1」这个最主要的用例已由 progress() 直接解决;而「任意 length → 裸 number」(再用于 counter、line-height、z-index 这些接受 number 的地方)目前仍只有 tan(atan2()) 一条途径——CSS Values
规范里的「类型代数」(让 calc 直接做带单位除法)尚在提案。你当前浏览器对 calc(100px / 1px) 的原生支持,见上方兼容性表末尾的实测行。
6 · 🔗 相关链接
- CSS Type Casting to Numeric: tan(atan2()) Scalars · dev.to/janeori——本页主要来源:Jane Ori 发现并系统化这套类型转换,含 @property 定形与浏览器缺陷记录。
- atan2() · developer.mozilla.org——语法与类型规则:两个参数同类型即可,number / dimension / percentage 都行。
- @property · developer.mozilla.org——把自定义属性注册出类型,本页「先定形成 px 再消除单位」的关键一步。