Obeta

最新的TypedOM提供的新特性

Chrome 浏览器最近有一个关于Typed OM的重大的更新,这个特性非常强大,使我们对于DOM的样式操作更加的方便.

Chrome 浏览器最近有一个关于 Typed OM 的重大的更新,这个新特性将在chrome 66版本提供,如果你有chromium浏览器,可以立即更新获取最新的版本查看支持.

CSS Typed OM介绍

下面我们来看一段代码:

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

如你所见,attributeStyleMap属性是新增的,返回的是一个StylePropertyMap类型,可以通过get获取 html 中 style 属性中相关的 css 属性,而不会说像之前一样使用style.padding来获取值,获取到的还是字符串,而不是数字,如果你使用style.display === none那肯定会出错,因为值是空的,你有时候需要这样style.display ===.

StylePropertyMap类型是一个Map-like objects, 有以下这些get/set/keys/values/entries/has/delete/clear等方法.

// 下面三个都是等价的:
el.attributeStyleMap.set('opacity', 0.3);
el.attributeStyleMap.set('opacity', '0.3');
el.attributeStyleMap.set('opacity', CSS.number(0.3));
// el.attributeStyleMap.get('opacity').value === 0.3

// StylePropertyMaps 类型是可以迭代的:
for (const [prop, val] of el.attributeStyleMap) {
	console.log(prop, val.value);
}
// → opacity, 0.3

el.attributeStyleMap.has('opacity'); // true
el.attributeStyleMap.delete('opacity'); // 删除.
el.attributeStyleMap.clear(); // 删除所有.

返回这个类型的还有 stylesheet 规则:

// Stylesheet 规则.
const stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].styleMap.set('background', 'blue');

对我们有什么好处?

毫无疑问的是这肯定有好处,那么好处在哪里呢?一个新的技术或者功能出现那么肯定是为了解决某一类痛点.

  1. 一些 bug: 有经验的开发人员都遇到过如下的问题:
el.style.opacity += 0.1;
el.style.opacity === '0.30.1'; // 爆炸💥
  1. 算术运算与单位转换: 比如将px转换为cm绝对长度单位和做一些相关的数学运算.
  2. 值限制和处理:Typed OM可以处理大多数给的值,并进行处理.
  3. 更好的性能: 官方说可以提高 30%的性能...
  4. 错误处理:CSS有足够好的错误提示和处理.
  5. 不会再有el.style.backgroundColorel.style['background-color']的问题,你现在只需要匹配任何你写在 css 里面的属性.

你的浏览器是否支持Typed OM

你可以现在就开始是用尝试新的特性,加上下面的一个功能检查吧:

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

好了,下面开始介绍一些基础的API.

基础使用

大部分时候这样使用,获取值或者给某个属性赋值:

el.attributeStyleMap.set('margin-top', CSS.px(10));
// el.attributeStyleMap.set('margin-top', '10px'); // 字符串也是可以接受的
el.attributeStyleMap.get('margin-top').value; // 10
el.attributeStyleMap.get('margin-top').unit; // 'px'

// 使用 CSSKeyWorldValue, 也就是css里的一些关键字:
el.attributeStyleMap.set('display', new CSSKeywordValue('initial'));
el.attributeStyleMap.get('display').value; // 'initial'
el.attributeStyleMap.get('display').unit; // undefined

在以前我们如果为了获取某个元素的所有值需要使用getComputedStyle来获取计算值,相当于你开的开发者控制台里面的Elements面板里的Computed样式列表.

el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === '0.5'; // 还是字符串

但是现在我们可以这样:

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

注意这里他们的不同之处:window.getComputedStyle(element)返回的是计算过后的值,而element.computedStyleMap()返回的是原始的值,并不做任何处理.例如<div style="width: 100px"><p style="width: 80%"></p></div>, 当我们分别用上面两个 api 查询 p 元素的宽度的时候,他们分别最后拿到的值是'80px'80.

CSS numerical values(CSS 数值)

CSS OM包含了一个CSSNumericValue类型,此类型分以下两种:

1.CSSUnitValue - 包含一个单位类型的值,比如40px. 2.CSSMathValue - 包含一个或多个可计算类型的值,比如calc(100vh - 50rem).

CSSUnitValue

我们可以使用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'

我们还可以直接调用 new 一个:new CSSUnitValue(10, 'px'),下面是CSS.*的所有方法,更多内容可以去[点击这里]查看.

partial namespace CSS {
	CSSUnitValue number(double value);
	CSSUnitValue percent(double value);

	// <length>
	CSSUnitValue em(double value);
	CSSUnitValue ex(double value);
	CSSUnitValue ch(double value);
	CSSUnitValue ic(double value);
	CSSUnitValue rem(double value);
	CSSUnitValue lh(double value);
	CSSUnitValue rlh(double value);
	CSSUnitValue vw(double value);
	CSSUnitValue vh(double value);
	CSSUnitValue vi(double value);
	CSSUnitValue vb(double value);
	CSSUnitValue vmin(double value);
	CSSUnitValue vmax(double value);
	CSSUnitValue cm(double value);
	CSSUnitValue mm(double value);
	CSSUnitValue Q(double value);
	CSSUnitValue in(double value);
	CSSUnitValue pt(double value);
	CSSUnitValue pc(double value);
	CSSUnitValue px(double value);

	// <angle>
	CSSUnitValue deg(double value);
	CSSUnitValue grad(double value);
	CSSUnitValue rad(double value);
	CSSUnitValue turn(double value);

	// <time>
	CSSUnitValue s(double value);
	CSSUnitValue ms(double value);

	// <frequency>
	CSSUnitValue Hz(double value);
	CSSUnitValue kHz(double value);

	// <resolution>
	CSSUnitValue dpi(double value);
	CSSUnitValue dpcm(double value);
	CSSUnitValue dppx(double value);

	// <flex>
	CSSUnitValue fr(double value);
};

CSSMathValue

可以提供支持的都是 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)"

Nested expressions(嵌套表达式)

有时候计算的表达式非常的复杂,会非常的不好阅读,因此我们可以使用缩进和嵌套表达式:

new CSSMathSum(CSS.px(1), new CSSMathNegate(new CSSMathProduct(2, CSS.em(3))));
// calc(1px - 2 * 3em)

或者直接这样:

new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3)); // calc(1px + 2px + 3px)

算术操作

这个挺有用的,在否写时候你需要直接操作计算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))"

是不是很方便呢.

转换单位

CSSUnitValue对象支持将已经固定的单位转换为绝对[长度单位]:

// 将px单位转换为其它的绝对单位或者物理单位.
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 transform values(CSS 转换值)

我们可以生成一个CSSTransformValue对象,这个对象接受一个数组转换对象(比如CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY).

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

transform.toString(); // ransform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px)

上面的这段代码还是挺简单的,有时候业务或者需求需要超复杂的动画需求,如果还是上面这样写会写出很冗长的代码.

CSSTransformValue还有一个.toMatrix()方法,返回一个DOMMatrix类型,还可以判断 2D 还是 3D 变换.

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 {a: 1, b: 0, c: 0, d: 1, e: 10, …}

下面我们试试来用一个立方体做一个动画:

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.
})();
  1. 我们可以直接使用CSSMathValue对象更改值.
  2. 我们不需要直接操控 DOM 来更改值,比如box.style.transform = rotate(0,0,1,${newAngle}deg),而是直接操作CSSTransformValue对象,官方说这样可以提高动画性能...

CSS custom properties values (CSS 自定义属性值)

足够新的 Chrome 版本已经支持了var()函数,这个函数可以获取声明的 css 变量,比如我们这样:

body {
	--bgColor: #c8c8c8;
	background-color: var(--bgColor);
}

对于这类自定义属性,Typed OM也提供了支持.var()就对应了Typed OM里的CSSVariableReferenceValue对象,而它的值是CSSUnparsedValue,可以px,%,em等等.

const foo = new CSSVariableReferenceValue('--bgColor');
// CSSVariableReferenceValue {variable: "--bgColor", fallback: null}

// Fallback values:
const padding = new CSSVariableReferenceValue(
	'--default-padding',
	new CSSUnparsedValue(['8px'])
);
// padding.variable === '--default-padding'
// padding.fallback instanceof CSSUnparsedValue === true
// padding.fallback[0] === '8px'

但是目前来看 Typed OM 是没有接口来获取和设置已经存在的 CSS 变量值的,只能通过以下的方法:

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

Position values(方位值)

CSSPositionValue对象包含两个属性x/y:

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

Parsing values(格式化值)

Typed OM可以帮助你格式化输入,返回相应的类型值.

  1. 可以使用CSSStyleValue格式化所有的属性类型:
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)'
  1. CSSNumericValue格式化所有的值类型:
CSSNumericValue.parse('42.0px'); // {value: 42, unit: 'px'}
CSSNumericValue.parse('calc(1px+2px)'); // CSSMathSum {values: CSSNumericArray, operator: "sum"}

// 最方便的还是这样:
CSS.px(42.0); // '42px'

错误处理

当你格式化的时候可以捕获到错误:

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

引用

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.