更高效、更安全地操做 CSSOM :CSS Typed OM

前言

長期以來,咱們要修改 DOM 元素的樣式,咱們實際上操做的是 CSS 的對象模型 CSSOM。而 Houdini 中推動的又一組 CSS 對象模型 Typed OM,該標準又給咱們帶來了什麼好處呢?javascript

CSSOM

CSSOM 是幹嗎的?

簡單的說來,CSSOM 是一組能讓 JS 操做元素 CSS 的 API。在瀏覽器進行頁面渲染的過程當中扮演着很是重要的角色,瀏覽器的渲染步驟大體包括:css

  1. 解析 HTML 內容並構建成 DOM 對象
  2. 解析 CSS 內容並構建成 CSSOM 對象
  3. 瀏覽器將 DOM 與 CSSOM 組合成渲染樹
  4. 最終瀏覽器將結果進行渲染

面臨的問題

在平時開發中,咱們經過元素上的 style 對象去獲取元素的樣式:html

const cover = document.getElementById('cover');
cover.style.opacity; // 假設是 0.5
複製代碼

而後咱們基於這個減小一點透明度:java

cover.style.opacity += 0.3;
複製代碼

那麼,這樣作有沒有問題呢?首先咱們來看看值的類型:git

typeof cover.style.opacity; // string
複製代碼

What a suprise!,因此實際上,上面減小透明度的操做實際上產生了 0.50.3 的值,很顯然這是個有問題的操做。對於 height 等屬性,一樣的,返回了相似 200px 的字符串,getComputedStyles 返回的數值也不例外。若是你想要將這些獲取出來的數值套入一些列的數學計算中,你必須先將其轉換成數字對象。爲了解決這些問題,Typed OM 出現了。github

Typed OM

Typed OM 的出現,給咱們讀取以及設定數值添加了一種新的方法,不一樣於 CSSOM 中原有的字符串值的表現形式,Typed OM 將 CSSOM 的數值以 map 的形式展示在元素的 attributeStyleMap 中,規則所對應的值則是更有使用價值的 JavaScript 對象。web

帶來的好處

  1. 更少的 bug,正如前面所展現的操做,經過 TypedOM 進行操做減小此類型的問題;
  2. 在數值對象上調用簡單的算術運算方法,絕對單位之間還能方便得盡興單位轉換;
  3. 更好的性能,因爲減小了字符串操做,對於 CSSOM 的操做性能獲得了更進一步的提高,由 Tab Akins 提供的測試代表,操做 Typed OM 比直接操做 CSSOM 字符串帶來了大約 30% 的速度提高;
  4. 錯誤處理,對於錯誤的 CSS 值,將會拋出錯誤;
  5. 鍵名與常規 CSS 寫法保持一致,不用在 backgroundColor 和 background-color 的邊緣試探;
  6. 因爲 attributeStyleMap 以及 computedStyleMap 對象是個 map,這樣意味着咱們可使用標準 map 中提供的全部方法。

瀏覽器支持狀況

目前各大瀏覽器廠商的實現狀況:瀏覽器

Intent to implement: 有意向實現佈局

Shipped: 已發佈性能

No signal: 暫無心圖

其中 Google Chrome 和 Opera 瀏覽器分別在 66 和 53 版中實現了。

可用性檢測方法

能夠經過如下方法檢測是否可用:

window.CSS && CSS.number
複製代碼

使用

基本的讀取和賦值方法

在 Typed OM 中,數值和數值的單位是分開的,所獲取的是一個 CSSUnitValue 對象,內置數值 value 和單位 unit 兩個鍵。

// 要對一個元素的樣式賦值,除了可使用 CSS.px 構建以外,還能接受字符串
el.attributeStyleMap.set('height', CSS.px(10));
el.attributeStyleMap.set('height', '10px');

// 對於獲取,返回 CSSUnitValue 對象,訪問其 value 屬性便可獲得數字類型的值
el.attributeStyleMap.get('height').value; // 10
el.attributeStyleMap.get('height').unit; // 'px'
複製代碼

CSS 數值類型

在 Typed OM 中,咱們有兩種基本的數值類型,一種是上面例子中提到的數字加單位的簡單數值,他們屬於 CSSUnitValue 類型。對於不止於單個數字加單位或使用calc計算的表達式,均屬於 CSSMathValue類型。

CSSUnitValue

如上所述,CSSUnitValue 表達了簡單的數字加單位的 CSS 數值,同時你也能夠經過對其使用 new 來構造一個,大多數狀況下,你還能從 CSS 對象下的同名方法直接構造:

const num = CSS.number('10');
// num.value -> 10 num.unit -> 'number'

const px = CSS.px(42);
// px.value -> 42 px.unit -> 'px'

// 一樣可使用 new 方法構造一個
const deg = new CSSUnitValue(45, 'deg');
// deg.value -> 45 deg.unit -> 'deg'
複製代碼

完整的方法列表,能夠查看 CSS Typed OM 草案的內容。

CSSMathValue

若是你要表達涉及不止一個數值以及使用 calc 計算表達式的數值,則須要使用 CSSMathValue。須要注意的是, calc 在實際使用中被瀏覽器求值以後,獲取到的是運算結果,也就是一個 CSSUnitValue 值。

既然涉及到表達式,天然少不了操做符,CSSMathValue 中還提供了基本的數學操做符:

// 求和操做: calc(100vw + -10px)
new CSSMathSum(CSS.vw(100), CSS.px(-10));

// 求積: calc(45deg * 3.1415926)
new CSSMathProduct(CSS.deg(45), CSS.number(Math.PI));

// 取相反數: calc(-10px)
new CSSMathNegate(CSS.px(10));

// 取倒數: calc(1 / 10px);
new CSSMathInvert(CSS.px(10));

// 範圍限制: calc(1px);
// 其中第一個參數爲最小值,第三個參數爲最大值,中間數值爲須要鉗制的數值
new CSSMathClamp(1, -1, 3);

// 最大值: max(10%, 10px)
new CSSMathMax(CSS.percent(10), CSS.px(10));

// 最小值: min(10%, 10px)
new CSSMathMin(CSS.percent(10), CSS.px(10));
複製代碼

表達式須要更復雜的怎麼辦?

以上的數學操做表達式符號均支持嵌套使用,例如須要構建表達式:calc(1px * (3px + 2em)),能夠作以下嵌套實現:

new CSSMathProduct(CSS.px(1), new CSSMathSum(CSS.px(3), CSS.em(2)));
複製代碼

數學操做方法

CSSMathValueCSSUnitValue 他們均繼承自 CSSNumericValue,天然地也繼承了CSSNumericValue 上的數學操做方法,方便使用:

// 加: 1px + 1px 
CSS.px(1).add(1);

// 減: 1px - 1px
CSS.px(1).sub(1);

// 乘: 1px * 3px
CSS.px(1).mul(3);

// 除: 1px 除 3px
CSS.px(1).div(3);

// 比較最大值: max(50%, 50vw);
CSS.percent(50).max(CSS.vw(50));

// 比較最小值: min(50vh, 50vw);
CSS.vh(50).min(CSS.vw(50));

// 相等比較方法,返回一個布爾值 true
CSS.px(200).equals(CSS.px(200));
複製代碼

同時,加減乘除這些操做一樣支持多個參數使用

// 累加 calc(10px + 10vw + 10%)
CSS.px(10).add(CSS.vw(10), CSS.percent(10));
複製代碼

除此以外,絕對單位之間還能相互轉換:

CSS.in(9).to('cm').value; 
// 22.860000000000003
複製代碼

CSS Transform 數值類型

對於 CSS Transform 的 transform 屬性,上面的基本數值表達徹底沒法知足,從而須要藉助 CSSTransformValue ,構建 CSSTransformValue能夠傳入如下幾種參數:

  1. CSSRotate: 旋轉
  2. CSSScale: 縮放
  3. CSSSkew:傾斜
  4. CSSSkewX:X 軸傾斜
  5. CSSSkewY: Y 軸傾斜
  6. CSSTranslate: 轉換
  7. CSSPerspective: 視角

與日常的 CSS 用法同樣,skew(x, y) 與分別 skewX(x) skewY(y) 產生的結果也是不同的,這點須要注意一下

用起來也是一樣的簡單:

// 轉變 transform: rotateX(45deg) scale(0.5) translate3d(10px, 10px, 10px);
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))
]);
複製代碼

對於CSSTranslate 類型,你還能夠訪問對象上的 is2D 方法查看當前 translate 是 2D 的仍是 3D 的。同時,還能調用 toMatrix 方法得到 DOMMatrix 矩陣對象。

CSS 位置數值類型

對於須要描述 x/y 位置的屬性,例如 object-position,則須要用到 CSSPositionValue 類型。

const pos = new CSSPositionValue(CSS.px(5), CSS.px(10));
// pos.y.value -> 10 pos.x.value -> 10
複製代碼

數值解析

既然咱們能夠在 Type OM 的對象上使用 toString() 方法獲得字符串規則,那麼咱們是否能經過 API 將字符串規則解析成 Type OM 的類型呢?答案是能夠的。使用 CSSStyleValue 中的 parse 方法便可:

CSSStyleValue.parse('transform', 'translate(10px) scale(0.5)');
// 將會解析成 CSSTransformValue 對象

CSSStyleValue.parse('height', '2px');
// 將會解析成 CSSUnitValue 類型
複製代碼

computedStyleMap

與傳統調用 window.getComputedStyle 方法相同,元素上的 computedStyleMap 方法一樣會返回全部的計算後屬性值。但它們仍然有一些小區別。window.getComputedStyle 仍然會返回字符串數值;而對於 computedStyleMap 方法來講,返回的數值則是轉換成 Type OM 數值類型的。

document.body.attributeStyleMap.set('opacity', 1); 

document.body.computedStyleMap().get('opacity').value;
// 1

window.getComputedStyle(document.body).opacity;
// '1'
複製代碼

Typed OM 在 Houdini 其餘標準中的角色

既然 Typed OM 涉及到了 CSSOM 的數值,那麼與之相關的標準中的數值都將與此相關。前段時間看了安佳老師的文章《CSS Paint API》的同窗可能會對裏面的棋盤例子有印象,CSS Paint API 對 paint 參數的輸入值其實也是 CSS Typed OM 中的數值類型。

除此以外,Typed OM 的使用在爲日後更高效地發展各個 Houdini 標準打下了基礎(包括自定義屬性,佈局以及繪製相關標準)。

總結

CSS Typed OM 解決了開發時修改數值的問題,同時經過減小字符串操做增長了整體的操做性能,使得咱們在操做 CSSOM 不只方便還高效,配合 requestAnimationFrame 還能製做出性能更優的自定義動畫。

參考連接

drafts.css-houdini.org/css-typed-o…

developers.google.com/web/updates…

rocks1635.rssing.com/chan-409413…

致謝

感謝安佳老師對本文提出的修改建議

相關文章
相關標籤/搜索