写过 JS 的人都踩过这个坑——CSS 属性读了半天还是个字符串,今天 CSS Typed OM 把这件事彻底原生化了
每次用 JS 读 CSS 属性都要和字符串打交道:el.style.opacity 返回的是 "0.3" 这种字符串,不是数字。想做个简单的加法都要 parseFloat 转一圈。更坑的是,你以为自己在做加法:
el.style.opacity += 0.1; // 结果是 "0.30.1",不是 0.4!
Firefox 155 稳定版把这件事彻底原生化了——CSS Typed OM 让所有 CSS 属性在你的 JS 里以类型化对象的形式存在,读出来是什么类型、写进去还是什么类型,数学运算不用再绕弯。
为什么字符串式的 CSSOM 一言难尽
传统 CSSOM 暴露的属性值全是字符串:
const width = div.style.width; // "200px",字符串
const opacity = window.getComputedStyle(div).opacity; // "0.5",字符串
这带来一连串问题:
- 数值运算要手动 parse:parseFloat、parseInt、Number(),每个属性都要转一遍
- 字符串拼接 bug:opacity 加 0.1 变成字符串拼接而不是数值加法
- 单位处理靠猜:
200px要自己截字符串才知道单位是什么 - 自定义属性读不到 computed 值:只能读行内样式,computed 样式里的变量一概不知
Typed OM 把这些问题全部用类型系统封死了——值是什么类型,读出来就是什么类型。
Typed OM 核心:两种 StylePropertyMap
Typed OM 的核心是 StylePropertyMap,一个类 Map 对象,存放属性名到类型化值的映射。每个 CSS 属性在里面都是强类型的:opacity 是 number、width 是 CSSUnitValue、color 是 CSSStyleValue。
浏览器给了两套接口:
读 computed 样式:computedStyleMap()
const styleMap = element.computedStyleMap();
const opacity = styleMap.get(opacity);
console.log(opacity.value, opacity.unit); // 0.5, "number"
const width = styleMap.get(width);
console.log(width.value, width.unit); // 200, "px"
// 自定义属性也能读 computed 值
const brandColor = styleMap.get(--brand-color);
返回的是 StylePropertyMapReadOnly,只读,但能读到浏览器计算后的完整值。
写行内样式:attributeStyleMap
element.attributeStyleMap.set(opacity, CSS.number(0.3));
element.attributeStyleMap.set(width, CSS.px(200));
element.attributeStyleMap.set(transform, CSS.rotate(45));
// 读出来是什么类型,加进去还是什么类型
const op = element.attributeStyleMap.get(opacity);
op.value += 0.1; // 0.4,不是 "0.30.1"
element.attributeStyleMap.set(opacity, CSS.number(op.value));
写属性不需要字符串拼接了。CSS.px()、CSS.deg()、CSS.percent() 这些工厂函数替你把单位封装好,引擎内部做类型检查。
delete()——终于可以删单个属性了
这是 Firefox 155 新增的支持(也是 Firefox 独家一段时间):
// 以前删样式只能整个 clear
// element.attributeStyleMap.clear();
// 现在可以删一个
element.attributeStyleMap.delete(opacity);
配合 .has() 查询和 .set() 覆写,形成了完整的 CRUD 操作——增删改查,全都是类型安全的。
if (element.attributeStyleMap.has(opacity)) {
const op = element.attributeStyleMap.get(opacity);
element.attributeStyleMap.set(opacity, CSS.number(op.value * 0.8));
}
迭代器支持——批量操作
StylePropertyMap 是可迭代的:
for (const [prop, val] of element.attributeStyleMap) {
if (val instanceof CSSUnitValue && val.unit === px) {
console.log(prop, val.value);
}
}
以前要读所有属性只能正则匹配 cssText,现在直接 for…of 遍历类型化对象。
实际场景:动画插值
Typed OM 最有价值的场景之一是动画计算。以前做动画插值,读取 opacity 后要手动 lerp:
// 以前
const from = parseFloat(getComputedStyle(el).opacity);
const to = 1;
const t = 0.5;
el.style.opacity = String(from + (to - from) * t); // 手动 lerp
现在直接操作 number:
// Typed OM
const from = el.computedStyleMap().get(opacity).value;
const to = 1;
const t = 0.5;
el.attributeStyleMap.set(opacity, CSS.number(from + (to - from) * t));
类型系统兜底,不用担心字符串拼接或单位丢失。
兼容性
Chrome 66+ 从 2018 年就开始支持 Typed OM,但 Firefox 一直缺失——直到 Firefox 154 Nightly 开始逐步支持,Firefox 155 稳定版补全了 StylePropertyMap.delete() 这个关键方法。Safari 暂未支持,约 70% 全球覆盖。
建议渐进增强:
if (element.attributeStyleMap) {
// Typed OM path
element.attributeStyleMap.set(opacity, CSS.number(0.5));
} else {
// Fallback string path
element.style.opacity = 0.5;
}
下一步
如果你在写任何涉及 CSS 数值读写的 JS——动画、拖拽、resize 监听、响应式断点检测——先检查一下你的代码里有多少 parseFloat 和字符串拼接。Typed OM 可以把这些全部替换掉,bug 会少很多。
文档:MDN CSS Typed OM / Chrome for Developers Houdini 指南
评论区
登录后可评论。