重點摘要
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
是類似地圖的物件,因此支援所有一般問題 (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 比舊版物件模型更加詳盡。我同意!
開始寫入型別 OM 之前,請先考慮其為資料表帶來的幾個主要功能:
錯誤數量較少。例如,數字值一律以數字形式傳回,而非字串。
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
算術運算和單位轉換:在絕對長度單位 (例如
px
->cm
) 和基本數學之間進行轉換。值取值範圍和四捨五入:輸入 OM 捨入和/或取值範圍值,使其位於屬性的允許範圍內。
成效更上一層樓:瀏覽器需要減少序列化和去序列化字串值的工作。現在,引擎對 JS 和 C++ 中的 CSS 值會有類似的瞭解。Tab Akins 展現的早期基準測試,比起使用舊版 CSSOM 和字串,可將類型 OM 的輸入速度提升約 30%。如果想使用
requestionAnimationFrame()
快速執行 CSS 動畫,這種情況可能會極為明顯。crbug.com/808933 可追蹤 Blink 中的其他效能工作。「我該使用駝峰式大小寫的 CSS 名稱或字串嗎?」不必再猜測名稱是駝峰式大小寫或字串 (例如
el.style.backgroundColor
和el.style['background-color']
)。類型 OM 中的 CSS 資源名稱一律為字串,並與您在 CSS 中編寫的內容相符 :)
瀏覽器支援和功能偵測
輸入的 OM 顯示在 Chrome 66 中,目前在 Firefox 中實作。Edge 會顯示支援跡象,但尚未將其加入平台資訊主頁。
針對特徵偵測,您可以查看是否已定義其中一個 CSS.*
數字工廠:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
API 基本資訊
存取樣式
值與 CSS 類型 OM 中的單位分開。取得樣式會傳回包含 value
和 unit
的 CSSUnitValue
:
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] 超出可接受的範圍值。計算樣式時,打類型的 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 數值
在 OM 型別中,數字會以兩種 CSSNumericValue
物件類型表示:
CSSUnitValue
- 包含單一單位類型的值 (例如"42px"
)。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
建立,並傳遞轉換值陣列 (例如 CSSRotate
、CSScale
、CSSSkew
、CSSSkewX
、CSSSkewY
)。舉例來說,假設您想重新建立這個 CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
轉譯為型別 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))
]);
除了詳細程度 (lolz!) 外,CSSTransformValue
有一些很酷的功能。其中包含可區分 2D 和 3D 轉換的布林值屬性,以及傳回轉換的 DOMMatrix
表示法的 .toMatrix()
方法:
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.
})();
請注意:
- 數值代表我們能直接用數學將角度增加!
- 動畫不會碰觸每個影格的 DOM 或讀取每個影格的值 (例如沒有
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
),而是透過更新基礎CSSTransformValue
資料物件改善效能來驅動動畫。
示範模式
如果您的瀏覽器支援 Typed OM ,下方就會顯示紅色方塊。方塊會在您將滑鼠遊標懸停在方塊上開始旋轉。動畫是由 CSS 類型 OM 技術提供!🤘
CSS 自訂屬性值
CSS var()
會成為類型 OM 中的 CSSVariableReferenceValue
物件。
系統會將這些值剖析為 CSSUnparsedValue
,因為它們可以使用任何類型 (px、%、em、 rgba() 等)。
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 位置 (例如 object-position
) 的 CSS 屬性是以 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
剖析值
型別 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 有點複雜,但希望這個 API 能產生較少錯誤,並大幅降低程式碼執行效能。