CSS Typed Object Modelの概要

# 概要
CSS Typed OMがChrome66でリリースされ[New in Chrome 66](https://developers.google.com/web/updates/2018/04/nic66#cssom)と[Working with the new CSS Typed Object Model](https://developers.google.com/web/updates/2018/03/cssom)という記事が出ていたのでそれを読んでみてどんな感じかまとめた。

※記載しているコードは参考にした記事のものをそのまま拝借しております。

# 本題
これまでjavascriptからCSSプロパティを使用する際は、`element.style`を利用してCSSOMから値を取得していた。しかしながら、取得していた値の型はstringであった。

```js:こんなかんじでstring型
el.style.opacity = 0.3;
console.log(typeof el.style.opacity);
> 'string' // A string!?
```

ここで`opacity`プロパティの値を変える場合を例に挙げる。`opacity`プロパティを変更するには、取得したstring型の値をnumber型にcastし、値を変更してあげる必要があるが、これは理想的なことではない。

```javascript:parseFloatで型変換
function step(timestamp) {
  const currentOpacity = parseFloat(el.style.opacity); // こんな感じ
  const newOpacity = currentOpacity + 0.01;
  element.style.opacity = newOpacity;
  if (newOpacity <= 1) {
    window.requestAnimationFrame(step);
  }
}
```

そのため、CSS Typed OMでは従来のような`element.style`を利用してcssの値を取得するのではなく`.attributeStyleMap`プロパティもしくは`.styleMap`プロパティを利用する。

```javascript:こんなかんじ
// 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');
```

`.attributeStyleMap`プロパティと`.styleMap`プロパティでは`StylePropertyMap`オブジェクトを取得できるが、`StylePropertyMap`は`map-like`オブジェクトのため、get/set/keys/values/entriesの操作が可能で、取得した値に対して容易で柔軟に操作が可能である。

```js
// 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.
```

# 利点
これまでのCSSOMに対する操作と比較すると30%のパフォーマンス向上があるため、JavaScriptアニメーションにおいて重宝する。

```javascript:オブジェクトに対して以下のような操作も可能
el.attributeStyleMap.set('opacity', 0.3);
el.attributeStyleMap.has('opacity'); // true
el.attributeStyleMap.delete('opacity');
el.attributeStyleMap.clear(); // remove all styles
```
加えて、string型からnumber型への変換忘れによるバグを取り除いたり、プロパティへ代入する値を適切な値に変換してくれたりする。

```js:こんな感じに最適な値にしてくれる
el.style.opacity = 3;
const opacity = el.computedStyleMap().get('opacity').value;
console.log(opacity);
> 1   <- 3ではなく1が入っている
```

他にも単位変換や数式や値の一致を扱う際に利用できるメソッドも用意されている。

```javascript:単位変換
// 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
```

```javascript:数式の利用
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))"
```

```javascript:値の一致
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
```

エラーハンドリングもできるらしい。

```javascript:エラーハンドリング
try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}
```

# ブラウザでサポートされているかを判定
Chrome66では実装済み、Firefoxでは現在実装中であるためサポートされていない場合の処理も必要である。

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

# 結論
スタイルに関する操作を行う際のバグを減らしたりパフォーマンス向上を見込めるのでぜひ使ってみたいと感じた。