使用全新的 CSS 型別物件模型

TL;DR

CSS 現在提供適當的物件型 API,可在 JavaScript 中處理值。

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

從此不必再串連字串,也不會再遇到難以察覺的錯誤!

簡介

舊版 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; 

新的 CSS 型別 OM

新的 CSS 型別物件模型 (型別 OM) 是 Houdini 計畫的一部分,可為 CSS 值新增型別、方法和適當的物件模型,擴展這個世界觀。值會以 JavaScript 物件的形式公開,而非字串,方便您以高效 (且合理) 的方式操控 CSS。

您將透過元素的新 .attributeStyleMap 屬性和樣式表規則的 .styleMap 屬性存取樣式,而非使用 element.style。兩者都會傳回 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'); 

由於 StylePropertyMap 是類似 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. 

請注意,在第二個範例中,opacity 會設為字串 ('0.3'),但稍後讀回屬性時,會傳回數字。

優點

那麼,CSS 型別 OM 試圖解決哪些問題?查看上述範例 (以及本文其餘部分),您可能會認為 CSS 型別 OM 比舊物件模型更冗長。我同意!

在放棄使用 Typed OM 前,請先瞭解這項功能提供的幾項重要功能:

  • 減少錯誤:例如,數值一律會以數字形式傳回,而非字串。

    el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons! 
  • 算術運算和單位換算:在絕對長度單位 (例如 px -> cm) 之間轉換,以及執行基本數學運算

  • 值限制和四捨五入。Typed OM 會將值四捨五入和/或限制在屬性的可接受範圍內。

  • 成效更上一層樓。瀏覽器需要執行的字串值序列化和還原序列化作業較少。現在,引擎在 JS 和 C++ 中使用類似的 CSS 值理解方式。Tab Atkins 顯示了一些早期效能基準,指出與使用舊版 CSSOM 和字串相比,Typed OM 的每秒作業數快了約 30%。這對於使用 requestionAnimationFrame() 的快速 CSS 動畫來說非常重要。crbug.com/808933 會追蹤 Blink 的其他效能工作。

  • 處理錯誤。全新的剖析方法為 CSS 帶來錯誤處理機制。

  • 「我應該使用駝峰式大小寫的 CSS 名稱或字串嗎?」您不必再猜測名稱是否為駝峰式大小寫或字串 (例如 el.style.backgroundColorel.style['background-color'])。Typed OM 中的 CSS 屬性名稱一律為字串,與您實際在 CSS 中編寫的內容相符 :)

瀏覽器支援和功能偵測

Chrome 66 已推出輸入型 OM,Firefox 則正在導入這項功能。Edge 已顯示支援跡象,但尚未新增至平台資訊主頁

如要進行特徵偵測,請檢查是否定義了其中一個 CSS.* 數值工廠:

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

API 基本資訊

存取樣式

在 CSS 型別物件模型中,值與單位是分開的。取得樣式會傳回包含 valueunitCSSUnitValue

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 

計算樣式

計算樣式已從 window 上的 API 移至 HTMLElement 的新方法 computedStyleMap()

舊版 CSSOM

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

新的已輸入 OM

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

值限制 / 四捨五入

新物件模型的一項優點是自動夾緊和/或捨入計算的樣式值。舉例來說,假設您嘗試將 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. 

CSS 數值

在 Typed OM 中,數字由兩種類型的 CSSNumericValue 物件表示:

  1. CSSUnitValue - 包含單一房型的值 (例如 "42px")。
  2. CSSMathValue - 含有多個值/單位的數值,例如數學運算式 (例如 "calc(56em + 10%)")。

單位值

簡單的數值 ("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' 

如需 CSS.* 方法的完整清單,請參閱規格

數學值

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

算術運算

CSS 型別 OM 最實用的功能之一,就是可對 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))" 

轉換

絕對長度單位 可以轉換為其他長度單位:

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

平等

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 轉換值

CSS 轉換是使用 CSSTransformValue 建立,並傳遞轉換值陣列 (例如 CSSRotateCSScaleCSSSkewCSSSkewXCSSSkewY)。舉例來說,假設您要重新建立這個 CSS:

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

翻譯成 Typed OM:

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

除了冗長 (笑!),CSSTransformValue提供一些實用功能。這個類別具有布林屬性,可區分 2D 和 3D 變形,以及 .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 轉換,為立方體製作動畫。

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. 數值表示我們可以透過數學直接遞增角度!
  2. 動畫是由更新基礎 CSSTransformValue 資料物件所驅動,而非在每個影格上觸控 DOM 或讀回值 (例如沒有 box.style.transform=`rotate(0,0,1,${newAngle}deg)`),因此可提升效能。

示範

如果瀏覽器支援 Typed OM,下方會顯示紅色立方體。將滑鼠游標懸停在方塊上時,方塊就會開始旋轉。動畫是由 CSS 型別 OM 驅動!🤘

CSS 自訂屬性值

CSS var() 會在型別 OM 中變成 CSSVariableReferenceValue 物件。因為這些值可以採用任何型別 (px、%、em、rgba() 等),所以會剖析為 CSSUnparsedValue

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> 

位置值

採用以空格分隔的 x/y 位置的 CSS 屬性 (例如 object-position) 會以 CSSPositionValue 物件表示。

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 

剖析值

Typed OM 為網路平台引進了剖析方法!這表示您終於可以以程式輔助方式剖析 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' 

處理錯誤

範例 - 檢查 CSS 剖析器是否會接受這個 transform 值:

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

結論

很高興 CSS 終於有更新的物件模型。處理字串時,我從未覺得得心應手。CSS Typed OM API 有點冗長,但希望這能減少錯誤,並在後續產生效能更高的程式碼。