[译] 开始使用新的 CSS Typed Object Model

0. 媒介

如今,CSS 具有一个恰当的基于对象的 API 来处置惩罚 JavaScript 中的值。

el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'

手动拼接字符串和种种新鲜毛病的日子已完毕了!

注:Chrome 66 为 CSS 属性的
一个子集增添了 CSS Typed Object Model 的支撑 。

1. 引见

1.1 旧的 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;

1.2 新的 CSS Typed OM

作为 Houdini 事情的一部分,新的 CSS 范例对象模子(Typed OM), 经由过程给 CSS 值增添范例、要领和恰当的对象模子来举行扩大。值不再是字符串,而是作为 JavaScript 对象的值,用于提拔 CSS 的机能和越发合理的操纵。

你能够不运用 element.style,而是经由过程新的 .attributeStyleMap 属性来猎取元素和 .styleMap 属性来猎取款式表划定规矩。两者都返回一个 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');

由于 StylePropertyMaps 是类似 Map 的对象,所以它们支撑一切罕见的操纵(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.

请注重,在第 2 个示例中,opacity 设置为字符串('0.3'),但稍后回读属性时会返回一个数字。

假如给定的 CSS 属性支撑数字,Typed OM 将接收一个字符串作为输入,但老是返回一个数字!旧 CSSOM 和新 Typed OM 之间的类比就犹如
.className 的一步一步生长,终究有了本身的 API
.classList

2. 长处

那末 CSS Typed OM 试图处理什么问题?看一下上面的例子(以及本文的其余部分),您能够会以为 CSS Typed OM 比旧的对象模子冗杂很多。我赞同!

在您摒弃 Typed OM 之前,请斟酌它带来的一些主要特征:

  • 更少的bug。比方数字值老是以数字情势返回,而不是字符串。

    el.style.opacity += 0.1;
    el.style.opacity === '0.30.1' // dragons!
  • 算术运算和单元转换。在相对长度单元(比方 px -> cm)之间举行转换并举行基本的数学运算。
  • 数值局限限定和舍入。Typed OM 经由过程对值举行局限限定和舍入,以使其在属性的可接收局限内。
  • 更好的机能。浏览器必需做更少的事情序列化和反序列化字符串值。如今,关于 CSS 值,引擎能够对 JS 和 C++ 运用类似的明白。Tab Akins 已展现了一些初期的机能基准测试,与运用旧的 CSSOM 和字符串比拟,Typed OM 的运转速率快了 ~30%。这对运用 requestionAnimationFrame() 处置惩罚疾速 CSS 动画能够很主要 。crbug.com/808933 能够追踪 Blink 的更多机能演示。
  • 毛病处置惩罚。新的剖析要领带来了 CSS 天下中的毛病处置惩罚。
  • “我应当运用骆驼式的 CSS 称号照样字符串呢?” 你不再须要猜想名字是骆驼还或字符串(比方 el.style.backgroundColor vs el.style['background-color'])。Typed OM 中的 CSS 属性称号始终是字符串,与您现实在 CSS 中编写的内容一致:)

3. 浏览器支撑和功用检测

Typed OM 追随 Chrome 66 宣布,Firefox 也正在开辟中。Edge 已显示出支撑的迹象,但还没有将其增添到他们的 platform dashboard

注重:如今 Chrome 66+ 仅支撑 CSS 属性的
一个子集

关于功用检测,您能够运用以下代码:

if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}

4. API 基本

4.1 接见款式

在 CSS Typed OM 中,单元是离开的。猎取款式返回一个 CSSUnitValue,包括 valueunit:

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

4.2 盘算款式

Computed styles 已从 window 挪动到了 HTMLElement,新的要领是 computedStyleMap()

旧的CSSOM

el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!

新 Typed OM

el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5

注:
window.getComputedStyle()
element.computedStyleMap() 有一个不同点,前者返回剖析后的值,而后者返回盘算后的值。比方,Typed OM 保存百分比值(
width: 50%),而 CSSOM 将其剖析为长度(比方
width: 200px)。

数值局限限定/舍入

新对象模子的一个很好的功用是对盘算款式值举行自动局限束缚或舍入。举一个例子,假定你尝试为 opacity 设置一个超越可接收局限 [0,1] 的值。Typed 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.

5. CSS 数值

数字由 Typed OM 中 CSSNumericValue 对象的两种范例来示意:

  • CSSUnitValue – 包括单个单元范例(比方 "42px")的值。
  • CSSMathValue – 包括多个值/单元的值,如数学表达式(比方 "calc(56em + 10%)")。

5.1 单元值

简朴的数值("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'

注重:如示例所示,这些要领能够通报一个
Number
String 范例的数字。

请参阅范例以猎取完全的 CSS.* 要领列表

5.2 数学值

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)
);

5.3 算术运算

CSS Typed OM 最有效的功用之一是能够对 CSSUnitValue 对象实行数学运算。

5.3.1 基本操纵

基本操纵(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))"

5.3.2 改变

相对长度单元能够转换为其他单元长度:

// 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.49066rad"
CSS.s(2).to('ms').value // 2000

5.3.3 等值推断

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

6. CSS transform 值

运用 CSSTransformValue 能够建立 CSS 变更,参数为 transform 值构成的数组(比方 CSSRotateCSScaleCSSSkewCSSSkewXCSSSkewY)。作为一个例子,假定你想从新建立这个 CSS:

{
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
}

翻译成 TypedOM:

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))
]);

除了它的冗杂(lolz!)以外,CSSTransformValue 另有一些很酷的功用。它具有辨别二维和三维变更的布尔属性以及 .toMatrix() 返回 DOMMatrix 变更示意的要领:

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 transform 来为多维数据集制造动画。

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.
})();

请注重:

  • 数值(Numerical value)意味着我们能够直接运用数学要领增添角度!
  • 不须要操纵 DOM 或许在每一帧都读取当前的值(比方运用 box.style.transform=\rotate(0,0,1,${newAngle}deg)`),经由过程更新底层 CSSTransformValue` 数据对象来驱动动画,从而进步机能

演示

下面,假如您的浏览器支撑 Typed OM,您会看到一个赤色的立方体。当您将鼠标悬停在该立方体上时,该立方体最先扭转。动画由 CSS Typed OM 供应支撑!

7. CSS 自定义属性值

CSS 在 Typed OM 中 var() 成为一个 CSSVariableReferenceValue 对象。它们的值被剖析为 CSSUnparsedValue 由于它们能够采纳任何范例(px%emrgba() 等)。

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>

7.1 位置值

CSS 属性的位置值采纳空格分开的 x/y,比方 object-positionCSSPositionValue 对象示意。

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

7.2 剖析值

Typed OM 将剖析要领引入到 Web 平台!这意味着您能够在运用它之前以编程体式格局剖析 CSS 值!这个新功用能够捕捉 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'

7.3 毛病处置惩罚

例子 – 搜检 CSS 剖析器是不是相符 transform 值:

try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}

8. 结论

很愉快终究有了一个更新的 CSS 对象模子。我从来没有以为运用字符串很惬意。CSS Typed OM API 虽然有点冗杂,但愿望它能够削减毛病和提拔机能。

    原文作者:justjavac
    原文地址: https://segmentfault.com/a/1190000014037586
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞