要点
CSS 现在具有一个正确的基于对象的 API,用于处理 JavaScript 中的值。
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
串联字符串和细微 bug 的时代已经结束!
简介
旧 CSSOM
多年来,CSS 已拥有对象模型 (CSSOM)。事实上,每当您在 JavaScript 中读取/设置 .style
时,都是在使用它:
// Element styles.
el.style.opacity = 0.3;
typeof el.style.opacity === 'string' // Ugh. A string!?
// Stylesheet rules.
document.styleSheets[0].cssRules[0].style.opacity = 0.3;
新建 CSS 类型 OM
全新 CSS 类型化对象模型 (Typed OM) 是 Houdini 的一项成果,通过为 CSS 值添加类型、方法和适当的对象模型来扩展此世界视图。值不是字符串,而是作为 JavaScript 对象公开,以便于对 CSS 进行高效(且合理的)操作。
您将通过针对元素的新 .attributeStyleMap
属性和针对样式表规则的 .styleMap
属性(而不是使用 element.style
)访问样式。两者都会返回一个 StylePropertyMap
对象。
// Element styles.
el.attributeStyleMap.set('opacity', 0.3);
typeof el.attributeStyleMap.get('opacity').value === 'number' // Yay, a number!
// Stylesheet rules.
const stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].styleMap.set('background', 'blue');
由于 StylePropertyMap
是类似于映射的对象,因此它们支持所有常见怀疑对象(get/set/keys/values/entries),因此可以灵活地使用:
// All 3 of these are equivalent:
el.attributeStyleMap.set('opacity', 0.3);
el.attributeStyleMap.set('opacity', '0.3');
el.attributeStyleMap.set('opacity', CSS.number(0.3)); // see next section
// el.attributeStyleMap.get('opacity').value === 0.3
// StylePropertyMaps are iterable.
for (const [prop, val] of el.attributeStyleMap) {
console.log(prop, val.value);
}
// → opacity, 0.3
el.attributeStyleMap.has('opacity') // true
el.attributeStyleMap.delete('opacity') // remove opacity.
el.attributeStyleMap.clear(); // remove all styles.
请注意,在第二个示例中,opacity
设置为字符串 ('0.3'
),但是当稍后读取该属性时,系统会返回数字。
优势
那么 CSS Typed OM 想要解决什么问题呢?查看上面的示例(以及本文的其余部分),您可能会认为 CSS 类型化 OM 比旧的对象模型详细得多。我同意!
在核销 Typed OM 之前,请考虑它带来的一些关键功能:
错误更少。例如,数值始终以数字而非字符串形式返回。
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
算术运算和单位转换。在绝对长度单位(例如
px
->cm
)之间进行转换并进行基本数学运算。值限制和舍入。类型化 OM 舍入和/或限制值,使其处于媒体资源的可接受范围内。
更出色的性能。浏览器在序列化和反序列化字符串值方面可以减少工作。现在,引擎会对 JS 和 C++ 中的 CSS 值采用相似的理解。Tab Akins 展示了一些早期性能基准,与使用旧的 CSSOM 和字符串相比,类型化 OM 的每秒操作速度提高了约 30%。对于使用
requestionAnimationFrame()
的快速 CSS 动画,这一点非常重要。crbug.com/808933 用于跟踪 Blink 中的其他性能工作。“我应该使用驼峰式大小写的 CSS 名称或字符串吗?”不必再费心猜测名称是采用驼峰式大小写形式,还是采用字符串形式(例如
el.style.backgroundColor
与el.style['background-color']
)。类型化 OM 中的 CSS 属性名称始终是字符串,与您在 CSS 中实际编写的内容一致 :)
浏览器支持和功能检测
类型化 OM 已在 Chrome 66 中推出,并且正在 Firefox 中实现。Edge 已显示支持迹象,但尚未将其添加到其平台信息中心。
对于特征检测,您可以检查是否定义了某个 CSS.*
数字工厂:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
API 基础知识
访问样式
值与 CSS 类型 OM 中的单位是分开的。获取样式会返回包含 value
和 unit
的 CSSUnitValue
:
el.attributeStyleMap.set('margin-top', CSS.px(10));
// el.attributeStyleMap.set('margin-top', '10px'); // string arg also works.
el.attributeStyleMap.get('margin-top').value // 10
el.attributeStyleMap.get('margin-top').unit // 'px'
// Use CSSKeyWorldValue for plain text values:
el.attributeStyleMap.set('display', new CSSKeywordValue('initial'));
el.attributeStyleMap.get('display').value // 'initial'
el.attributeStyleMap.get('display').unit // undefined
计算出的样式
计算后的样式已从 window
上的 API 移至 HTMLElement
(computedStyleMap()
) 上的新方法:
旧版 CSSOM
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
新建类型化 OM
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
值限制 / 舍入
新对象模型的一个很棒的功能是自动限制和/或舍入计算的样式值。例如,假设您尝试将 opacity
设置为超出可接受范围 [0, 1] 的值。类型化 OM 会在计算样式时将值限定为 1
:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
同样,将 z-index:15.4
舍入为 15
,使值保持整数。
el.attributeStyleMap.set('z-index', CSS.number(15.4));
el.attributeStyleMap.get('z-index').value === 15.4 // val not rounded.
el.computedStyleMap().get('z-index').value === 15 // computed style is rounded.
CSS 数值
数字由类型化 OM 中两种类型的 CSSNumericValue
对象表示:
CSSUnitValue
- 包含单一单位类型的值(例如"42px"
)。CSSMathValue
- 包含多个值/单位的值,如数学表达式(例如"calc(56em + 10%)"
)。
单位值
简单数值 ("50%"
) 由 CSSUnitValue
对象表示。虽然您可以直接创建这些对象 (new CSSUnitValue(10, 'px')
),但在大多数情况下,您将使用 CSS.*
工厂方法:
const {value, unit} = CSS.number('10');
// value === 10, unit === 'number'
const {value, unit} = CSS.px(42);
// value === 42, unit === 'px'
const {value, unit} = CSS.vw('100');
// value === 100, unit === 'vw'
const {value, unit} = CSS.percent('10');
// value === 10, unit === 'percent'
const {value, unit} = CSS.deg(45);
// value === 45, unit === 'deg'
const {value, unit} = CSS.ms(300);
// value === 300, unit === 'ms'
请参阅 CSS.*
方法完整列表的规范。
数学值
CSSMathValue
对象表示数学表达式,通常包含多个值/单位。常见的示例是创建 CSS calc()
表达式,但也有一些适用于所有 CSS 函数的方法:calc()
、min()
、max()
。
new CSSMathSum(CSS.vw(100), CSS.px(-10)).toString(); // "calc(100vw + -10px)"
new CSSMathNegate(CSS.px(42)).toString() // "calc(-42px)"
new CSSMathInvert(CSS.s(10)).toString() // "calc(1 / 10s)"
new CSSMathProduct(CSS.deg(90), CSS.number(Math.PI/180)).toString();
// "calc(90deg * 0.0174533)"
new CSSMathMin(CSS.percent(80), CSS.px(12)).toString(); // "min(80%, 12px)"
new CSSMathMax(CSS.percent(80), CSS.px(12)).toString(); // "max(80%, 12px)"
嵌套表达式
使用数学函数创建更复杂的值有点令人困惑。下面是一些入门示例。我添加了额外的缩进 使其更易于阅读
calc(1px - 2 * 3em)
将构建为:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
将构建为:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
将构建为:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
算术运算
CSS 类型化 OM 最有用的功能之一是,可以对 CSSUnitValue
对象执行数学运算。
基本操作
支持基本操作 (add
/sub
/mul
/div
/min
/max
):
CSS.deg(45).mul(2) // {value: 90, unit: "deg"}
CSS.percent(50).max(CSS.vw(50)).toString() // "max(50%, 50vw)"
// Can Pass CSSUnitValue:
CSS.px(1).add(CSS.px(2)) // {value: 3, unit: "px"}
// multiple values:
CSS.s(1).sub(CSS.ms(200), CSS.ms(300)).toString() // "calc(1s + -200ms + -300ms)"
// or pass a `CSSMathSum`:
const sum = new CSSMathSum(CSS.percent(100), CSS.px(20)));
CSS.vw(100).add(sum).toString() // "calc(100vw + (100% + 20px))"
转化
绝对长度单位可转换为其他单位长度:
// Convert px to other absolute/physical lengths.
el.attributeStyleMap.set('width', '500px');
const width = el.attributeStyleMap.get('width');
width.to('mm'); // CSSUnitValue {value: 132.29166666666669, unit: "mm"}
width.to('cm'); // CSSUnitValue {value: 13.229166666666668, unit: "cm"}
width.to('in'); // CSSUnitValue {value: 5.208333333333333, unit: "in"}
CSS.deg(200).to('rad').value // 3.49066...
CSS.s(2).to('ms').value // 2000
等式
const width = CSS.px(200);
CSS.px(200).equals(width) // true
const rads = CSS.deg(180).to('rad');
CSS.deg(180).equals(rads.to('deg')) // true
CSS 转换值
CSS 转换是使用 CSSTransformValue
创建的,并传递了一组转换值(例如 CSSRotate
、CSScale
、CSSSkew
、CSSSkewX
、CSSSkewY
)。例如,假设您要重新创建以下 CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
已转换为类型化 OM:
const transform = new CSSTransformValue([
new CSSRotate(CSS.deg(45)),
new CSSScale(CSS.number(0.5), CSS.number(0.5)),
new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10))
]);
除了详细程度外(哈哈!),CSSTransformValue
具有一些很棒的功能。它具有用于区分 2D 和 3D 转换的布尔值属性,以及用于返回转换的 DOMMatrix
表示法的 .toMatrix()
方法:
new CSSTranslate(CSS.px(10), CSS.px(10)).is2D // true
new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10)).is2D // false
new CSSTranslate(CSS.px(10), CSS.px(10)).toMatrix() // DOMMatrix
示例:以动画方式呈现立方体
我们来看一个使用转换的实际示例。我们将使用 JavaScript 和 CSS 转换 为立方体添加动画效果
const rotate = new CSSRotate(0, 0, 1, CSS.deg(0));
const transform = new CSSTransformValue([rotate]);
const box = document.querySelector('#box');
box.attributeStyleMap.set('transform', transform);
(function draw() {
requestAnimationFrame(draw);
transform[0].angle.value += 5; // Update the transform's angle.
// rotate.angle.value += 5; // Or, update the CSSRotate object directly.
box.attributeStyleMap.set('transform', transform); // commit it.
})();
请注意:
- 数值意味着我们可以用数学公式直接增加角度!
- 通过更新底层
CSSTransformValue
数据对象,提高性能来驱动动画,而不是轻触 DOM 或读回每一帧的值(例如,没有box.style.transform=`rotate(0,0,1,${newAngle}deg)`
)。
演示
如果您的浏览器支持类型化 OM,您会在下方看到一个红色立方体。当您将鼠标悬停在立方体上时,立方体开始旋转。此动画由 CSS Typed OM!🤘
CSS 自定义属性值
CSS var()
成为类型化 OM 中的 CSSVariableReferenceValue
对象。它们的值会被解析为 CSSUnparsedValue
,因为它们可以接受任何类型(px、%、em、 rgba() 等)。
const foo = new CSSVariableReferenceValue('--foo');
// foo.variable === '--foo'
// Fallback values:
const padding = new CSSVariableReferenceValue(
'--default-padding', new CSSUnparsedValue(['8px']));
// padding.variable === '--default-padding'
// padding.fallback instanceof CSSUnparsedValue === true
// padding.fallback[0] === '8px'
如果您想要获取自定义属性的值,还需要执行几项操作:
<style>
body {
--foo: 10px;
}
</style>
<script>
const styles = document.querySelector('style');
const foo = styles.sheet.cssRules[0].styleMap.get('--foo').trim();
console.log(CSSNumericValue.parse(foo).value); // 10
</script>
位置值
采用空格分隔 x/y 位置的 CSS 属性(例如 object-position
)由 CSSPositionValue
对象表示。
const position = new CSSPositionValue(CSS.px(5), CSS.px(10));
el.attributeStyleMap.set('object-position', position);
console.log(position.x.value, position.y.value);
// → 5, 10
解析值
Typed OM 为 Web 平台引入了解析方法!这意味着,您最终可以以编程方式解析 CSS 值,之后再尝试使用 CSS 值!这项新功能有望为您发现早期 bug 和格式不正确的 CSS,有望助您一臂之力。
解析完整样式:
const css = CSSStyleValue.parse(
'transform', 'translate3d(10px,10px,0) scale(0.5)');
// → css instanceof CSSTransformValue === true
// → css.toString() === 'translate3d(10px, 10px, 0) scale(0.5)'
将值解析为 CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
错误处理
示例 - 检查 CSS 解析器是否同意此 transform
值:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
总结
终于更新了 CSS 的对象模型,太好了。我从未接触过使用弦乐CSS Typed OM API 有点冗长,但有望降低 bug 数量并提高代码性能。