HTML
CSS
JavaScript

CSS Typed Object Modelの概要


概要

CSS Typed OMがChrome66でリリースされNew in Chrome 66Working with the new CSS Typed Object Modelという記事が出ていたのでそれを読んでみてどんな感じかまとめた。

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


本題

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


こんなかんじでstring型

el.style.opacity = 0.3;

console.log(typeof el.style.opacity);
> 'string' // A string!?

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


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プロパティを利用する。


こんなかんじ

// 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オブジェクトを取得できるが、StylePropertyMapmap-likeオブジェクトのため、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.


利点

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


オブジェクトに対して以下のような操作も可能

el.attributeStyleMap.set('opacity', 0.3);

el.attributeStyleMap.has('opacity'); // true
el.attributeStyleMap.delete('opacity');
el.attributeStyleMap.clear(); // remove all styles

加えて、string型からnumber型への変換忘れによるバグを取り除いたり、プロパティへ代入する値を適切な値に変換してくれたりする。


こんな感じに最適な値にしてくれる

el.style.opacity = 3;

const opacity = el.computedStyleMap().get('opacity').value;
console.log(opacity);
> 1 <- 3ではなく1が入っている

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


単位変換

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



数式の利用

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



値の一致

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


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


エラーハンドリング

try {

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


ブラウザでサポートされているかを判定

Chrome66では実装済み、Firefoxでは現在実装中であるためサポートされていない場合の処理も必要である。

if (window.CSS && CSS.number) {

// Supports CSS Typed OM.
}


結論

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