在前面的章节中,我们花了很多时间学习如何通过 CSSOM 操作样式——无论是操作样式表规则、修改内联样式,还是获取计算样式。但你可能已经注意到一个共同点:所有操作都是基于字符串的

回想一下你写过的代码:

// 读取样式 —— 得到的是字符串
const width = element.style.width;        // "200px"
const color = getComputedStyle(el).color; // "rgb(255, 0, 0)"

// 修改样式 —— 拼接字符串
element.style.width = (parseFloat(width) + 50) + 'px';
element.style.transform = 'translateX(' + x + 'px) rotate(' + deg + 'deg)';

字符串本身没有问题——毕竟 CSS 在本质上就是文本。但当我们需要 计算、转换、运算 时,字符串就成了一个麻烦:你需要 parseFloat() 提取数字,需要手动拼接单位,需要小心翼翼地处理格式。

这就是 CSS 类型对象模型(CSS Typed Object Model,简称 CSS Typed OM) 要解决的问题。

CSS Typed OM 是 CSSOM 的一个现代化升级。它将 CSS 值暴露为 类型化的 JavaScript 对象 而不是字符串——数值就是真正的 JavaScript 数字,单位被单独保存,并且定义了单位感知的数学运算。这意味着你可以像操作普通数字一样操作 CSS 值,让代码更清晰、更可靠,也通常更快。

重要提示: CSS Typed OM 目前属于 渐进增强技术,尚未在所有主流浏览器中完全支持。本章的内容旨在让你了解这项前沿技术,并能够在支持的环境中使用它。在实际项目中,请务必做好降级方案。

一、为什么需要 Typed OM?

在深入具体 API 之前,我们先理解一个问题:传统的字符串操作方式到底有什么问题?

1.1 字符串操作 CSS 的痛点

痛点一:解析麻烦

从 CSS 值中提取数字需要手动解析:

// 传统方式:字符串解析
const style = getComputedStyle(element);
const widthStr = style.width;           // "200.5px"
const width = parseFloat(widthStr);     // 200.5 —— 还要手动提取
const unit = widthStr.replace(/[\d.]/g, ''); // "px" —— 还要手动提取单位

// 做运算后再拼回去
const newWidth = width + 50;
element.style.width = newWidth + 'px';  // 又要手动拼接单位

每一步都涉及字符串操作,繁琐且容易出错。

痛点二:类型不安全

你无法在编译时(或运行时早期)发现类型错误。一个本该是数字的地方传入了字符串,可能要到样式不生效时才能发现:

// 没有人能阻止你这样做——直到样式出问题
element.style.width = 'hello';  // 不会报错,但样式无效
element.style.opacity = 'not a number'; // 同样的问题

痛点三:单位混乱

不同属性使用不同的单位——width 用 px,font-size 用 em 或 rem,transform 用 deg。手动管理这些单位很容易混淆。

痛点四:复杂属性难以操作

像 transform 这样的属性,值可能包含多个函数(如 translateX(10px) rotate(45deg)),用字符串拼接和解析简直是噩梦。

1.2 Typed OM 的优势

CSS Typed OM 正是为解决这些问题而生的。

优势一:类型安全

数值就是数值,单位就是单位。JavaScript 引擎可以更好地理解和优化这些操作:

// Typed OM 方式:直接操作数值
element.attributeStyleMap.set('width', CSS.px(200));
// 宽度就是 200px,清晰明了

优势二:单位感知

单位被保留为对象的一部分,你可以进行单位感知的运算:

const width = CSS.px(200);
const newWidth = width.add(CSS.px(50));  // CSS.px(250)
// 不同单位不能直接相加——类型系统会阻止错误

优势三:性能更好

传统的字符串操作需要反复构建和解析 CSS 字符串,而 Typed OM 允许直接操作底层值,然后廉价地转换回去。

优势四:代码更可维护

操作 CSS 值的逻辑变得更清晰、更可读,减少了字符串拼接带来的“魔法代码”。

二、核心接口

CSS Typed OM 提供了一套完整的接口体系。我们来逐一认识它们。

2.1 CSSStyleValue —— 所有 CSS 值的基类

CSSStyleValue 是 CSS Typed OM API 的 基类,所有可通过 Typed OM 访问的 CSS 值都继承自它。

简单理解:CSSStyleValue 就像是所有“类型化 CSS 值”的根。它的子类包括:

  • CSSNumericValue:带单位的数值(如 200px、50%)。

  • CSSKeywordValue:关键字值(如 auto、red)。

  • CSSTransformValue:变换值(如 translateX(10px))。

  • CSSUnparsedValue:未解析的值(如包含自定义属性的值)。

一个 CSSStyleValue 的实例可以在任何期望字符串的地方使用——这为渐进增强提供了便利。

2.2 CSSStyleValue.parse() 与 parseAll() —— 从字符串到类型化对象

有时候你已经有 CSS 字符串了(比如从 getComputedStyle 读取到的值),想把它转换成类型化的对象。这时候就用到了 parse() 和 parseAll()。

CSSStyleValue.parse(property, cssText) :解析一个 CSS 属性值,返回第一个匹配的 CSSStyleValue 对象。

// 从字符串解析出类型化的 CSS 值
const value = CSSStyleValue.parse('width', '200px');
console.log(value); // CSSNumericValue { value: 200, unit: 'px' }

// 可以直接使用
element.attributeStyleMap.set('width', value);

CSSStyleValue.parseAll(property, cssText) :解析一个 CSS 属性值,返回所有匹配的 CSSStyleValue 对象数组。

// 对于可以接受多个值的属性(如 background)
const values = CSSStyleValue.parseAll('background-image', 'url(a.png), url(b.png)');
console.log(values.length); // 2
console.log(values[0]); // CSSImageValue

这两个方法是从“字符串世界”通往“类型化世界”的桥梁。

2.3 StylePropertyMap —— CSS 声明块的新表示

StylePropertyMap 是 CSS Typed OM 中 最重要的接口之一。它提供了一种全新的方式来操作元素的样式声明,是传统 CSSStyleDeclaration(也就是 element.style)的替代方案。

要使用 StylePropertyMap,你需要通过元素的 attributeStyleMap 属性来访问它:

// 传统方式:CSSStyleDeclaration
element.style.width = '200px';
element.style.backgroundColor = 'blue';

// Typed OM 方式:StylePropertyMap
element.attributeStyleMap.set('width', CSS.px(200));
element.attributeStyleMap.set('background-color', CSS.color('blue'));

StylePropertyMap 提供了以下核心方法:

set(property, value) —— 设置一个 CSS 属性的值,会 覆盖 已有值:

// 设置单个值
element.attributeStyleMap.set('width', CSS.px(200));

// 设置多个值(如 background-image 可以有多层)
element.attributeStyleMap.set('background-image', [
    CSS.url('image1.png'),
    CSS.url('image2.png')
]);

append(property, value) —— 向一个已有属性 追加 值(而不是覆盖):

// 第一次设置
element.attributeStyleMap.set('transform', CSS.translate(10, 10));

// 追加一个旋转 —— 现在 transform 有两个值
element.attributeStyleMap.append('transform', CSS.rotate(45));

// 等价于 CSS: transform: translate(10px, 10px) rotate(45deg);

delete(property) —— 删除指定属性的声明:

element.attributeStyleMap.delete('width');
// 相当于 element.style.width = '';

clear() —— 删除所有声明:

element.attributeStyleMap.clear();
// 相当于 element.style = '';

get(property)getAll(property) —— 读取属性值:

// 获取单个值(如果属性有多个值,返回第一个)
const width = element.attributeStyleMap.get('width');
console.log(width.value); // 200
console.log(width.unit);  // 'px'

// 获取所有值(返回数组)
const transforms = element.attributeStyleMap.getAll('transform');
transforms.forEach(t => console.log(t.toString()));
2.4 关于 computedStyleMap()

除了 attributeStyleMap (操作内联样式),CSS Typed OM 还提供了 computedStyleMap() 方法,用于获取元素的 计算样式(相当于 getComputedStyle 的类型化版本)。

// 传统方式:getComputedStyle 返回字符串
const styles = getComputedStyle(element);
const width = styles.width; // "200px" —— 字符串

// Typed OM 方式:computedStyleMap 返回类型化对象
const styleMap = element.computedStyleMap();
const width = styleMap.get('width'); // CSSNumericValue { value: 200, unit: 'px' }
console.log(width.value); // 200 —— 真正的数字!

computedStyleMap() 返回的是一个 StylePropertyMapReadOnly 对象——顾名思义,它是只读的,只能用来读取计算样式,不能修改。

你还可以遍历 StylePropertyMapReadOnly 来查看元素的所有计算样式:

const styleMap = element.computedStyleMap();
for (const [prop, val] of styleMap) {
    console.log(`${prop}: ${val}`);
}
2.5 CSSUnparsedValue —— 处理自定义属性

CSS 自定义属性(CSS Variables)的值在 Typed OM 中由 CSSUnparsedValue 表示。

自定义属性的值可能包含变量引用(如 var(--foo)),这些值在解析之前是“未解析的”。CSSUnparsedValue 就是用来表示这种状态的。

// 设置一个包含自定义属性的值
element.attributeStyleMap.set('--theme-color', 'var(--base-color)');

// 读取它
const value = element.attributeStyleMap.get('--theme-color');
console.log(value instanceof CSSUnparsedValue); // true
console.log(value.toString()); // 'var(--base-color)'

CSSUnparsedValue 是可变的——你可以在它上面进行操作,修改其中的片段或变量引用。

2.6 CSSTransformValue —— 表示 transform 属性

transform 属性在 CSS 中非常特殊——它可以包含多个变换函数(如 translate、rotate、scale 等),每个函数又有自己的参数。用字符串来操作 transform 一直是个痛点。

CSSTransformValue 就是专门用来表示 transform 属性值的。

// 创建一个 transform 值:translate(10px, 20px) rotate(45deg)
const transform = new CSSTransformValue([
    CSS.translate(CSS.px(10), CSS.px(20)),
    CSS.rotate(CSS.deg(45))
]);

// 设置到元素上
element.attributeStyleMap.set('transform', transform);

// 读取并操作
const current = element.attributeStyleMap.get('transform');
console.log(current.length); // 2 —— 有两个变换函数
console.log(current.is2D);   // true —— 这是 2D 变换

// 添加一个新的变换
current.append(CSS.scale(1.5));
// 现在 transform 有三个函数了

CSSTransformValue 的实例属性包括:

  • length:变换函数的数量(只读)。

  • is2D:是否是 2D 变换(只读)。

它还有一个实用的方法 toMatrix(),可以将变换转换为 DOMMatrix 对象——这对于需要进行矩阵运算的场景非常有用。

三、浏览器支持与渐进增强策略

正如本章开头提到的,CSS Typed OM 目前尚未在所有主流浏览器中得到完全支持

当前支持状况

截至 2026 年 6 月:

  • Chrome / Edge:105+ 版本默认启用。

  • Safari:需要手动开启(开发 → 实验性功能 → CSS Typed OM)

  • Firefox:目前仍不支持 attributeStyleMap,仅有部分 computedStyleMap 实验性接口可用。

如何检测 Typed OM 是否可用

在实际项目中使用前,建议先进行特性检测:

function isTypedOMSupported() {
    return window.CSS && 
           typeof CSS.px === 'function' && 
           Element.prototype.attributeStyleMap !== undefined;
}

if (isTypedOMSupported()) {
    // 使用 Typed OM
    element.attributeStyleMap.set('width', CSS.px(200));
} else {
    // 降级到传统方式
    element.style.width = '200px';
}
渐进增强的实践建议

1. 特性检测是必须的:永远不要假设 Typed OM 可用,先用 if 判断再使用。

2. 封装一个样式操作工具:将 Typed OM 和传统方式封装在一个函数中,业务代码无需关心底层实现:

function setStyle(element, property, value) {
    if (isTypedOMSupported()) {
        // 假设 value 是 CSS.px(200) 这样的类型化对象
        element.attributeStyleMap.set(property, value);
    } else {
        // 降级:value 应该是字符串如 '200px'
        element.style[property] = value;
    }
}

3. 注意环境要求:Typed OM 需要在 HTTPS 或 localhost 协议下运行。

4. 不要在生产环境中强制依赖:除非你的目标用户群体明确使用支持 Typed OM 的浏览器,否则始终提供降级方案。

四、完整示例

下面是一个综合示例,展示 Typed OM 的完整使用流程:

<style>
   .box {
       width: 100px;
       height: 100px;
       background: #007bff;
       transition: all 0.3s ease;
    }
</style>

<div class="box" id="box"></div>
<button id="animateBtn">动画</button>

<script>
    const box = document.getElementById('box');
    const btn = document.getElementById('animateBtn');

    // 1. 检测 Typed OM 是否可用
    function isTypedOMSupported() {
        return window.CSS &&
            typeof CSS.px === 'function' &&
            Element.prototype.attributeStyleMap !== undefined;
    }

    // 2. 封装样式操作
    function setTypedStyle(element, property, value) {
        if (isTypedOMSupported()) {
            element.attributeStyleMap.set(property, value);
        } else {
            // 降级:需要传入字符串
            element.style[property] = value.toString ? value.toString() : value;
        }
    }

    // 3. 读取计算样式(类型化方式)
    function getComputedTyped(element, property) {
        if (isTypedOMSupported()) {
            return element.computedStyleMap().get(property);
        } else {
            return getComputedStyle(element)[property];
        }
    }

    // 4. 点击按钮执行动画
    btn.addEventListener('click', function() {
        if (isTypedOMSupported()) {
            // Typed OM 方式:类型化操作
            const currentWidth = box.computedStyleMap().get('width');
            const newWidth = CSS.px(currentWidth.value + 50);
            box.attributeStyleMap.set('width', newWidth);

            // transform 使用 CSSTransformValue
            const rotation = CSS.deg(45);
            const transform = new CSSTransformValue([
                CSS.rotate(rotation)
            ]);
            box.attributeStyleMap.set('transform', transform);

            // 背景色
            box.attributeStyleMap.set('background-color', CSS.color('tomato'));

            console.log('新宽度:', box.attributeStyleMap.get('width').value + 'px');
        } else {
            // 降级:传统字符串方式
            const currentWidth = parseFloat(getComputedStyle(box).width);
            box.style.width = (currentWidth + 50) + 'px';
            box.style.transform = 'rotate(45deg)';
            box.style.backgroundColor = 'tomato';
        }
    });

    // 5. 控制台测试
    console.log('Typed OM 可用:', isTypedOMSupported());
    if (isTypedOMSupported()) {
        const allStyles = box.computedStyleMap();
        console.log('所有计算样式:');
        for (const [prop, val] of allStyles) {
            console.log(`  ${prop}: ${val}`);
        }
    }
</script>

这个示例展示了:

  • Typed OM 可用性的检测。

  • 带降级的样式操作封装。

  • attributeStyleMap 设置样式。

  • computedStyleMap() 读取计算样式。

  • CSSTransformValue 操作 transform。

  • 与传统方式的对比。

五、小结

本章我们学习了 CSS 类型对象模型(CSS Typed OM)——CSSOM 的现代化升级:

1. 为什么需要 Typed OM

  • 传统字符串操作 CSS 存在诸多痛点:解析麻烦、类型不安全、单位混乱、复杂属性难以操作。

  • Typed OM 的优势:类型安全、单位感知、性能更好、代码更可维护。

2. 核心接口

  • CSSStyleValue:所有 Typed OM CSS 值的基类。

  • CSSStyleValue.parse() / parseAll():从 CSS 字符串解析为类型化对象。

  • StylePropertyMap:通过 element.attributeStyleMap 访问,提供 set()、append()、delete()、clear() 等方法。

  • computedStyleMap():获取计算样式的类型化版本,返回 StylePropertyMapReadOnly。

  • CSSUnparsedValue:表示包含自定义属性的值。

  • CSSTransformValue:表示 transform 属性值,支持 length、is2D 属性和 toMatrix() 方法

3. 浏览器支持与渐进增强

  • 目前 Chrome/Edge 105+ 默认支持,Safari 需手动开启,Firefox 支持有限。

  • 务必使用特性检测并提供降级方案。

  • 适合作为渐进增强技术使用,不应强制依赖。

CSS Typed OM 代表了 CSS 操作的发展方向。虽然目前还不能在所有项目中全面使用,但了解它、在合适的场景中尝试它,会让你走在技术前沿。随着浏览器支持的不断完善,它终将成为前端开发者的标准工具。

至此,我们完成了 CSSOM 系列的七章内容——从 CSSOM 概览到样式表获取、规则操作、内联样式、计算样式、CSSOM View,再到 CSS Typed OM。你已掌握了从“读取样式”到“修改样式”、从“字符串操作”到“类型化操作”的完整知识体系。