TL;DR
Il CSS ora dispone di un'API basata su oggetti appropriata per lavorare con i valori in JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
I giorni della concatenazione di stringhe e sottili insetti sono finiti.
Introduzione
CSSOM precedente
Il CSS dispone di un modello a oggetti (CSSOM) da molti anni. Infatti, ogni volta che leggi/imposti .style
in JavaScript, lo utilizzi:
// 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;
Nuovo OM di tipo CSS
Il nuovo modello a oggetti di tipo CSS (Typed Object Model), parte dell'impegno di Houdini, espande questa visione del mondo aggiungendo tipi, metodi e un modello a oggetti corretto ai valori CSS. Al posto delle stringhe, i valori vengono esposti come oggetti JavaScript per facilitare la manipolazione efficace (e sensibile) di CSS.
Anziché utilizzare element.style
, accederai agli stili tramite una nuova
proprietà .attributeStyleMap
per gli elementi e una proprietà .styleMap
per
le regole del foglio di stile. Entrambi restituiscono un oggetto 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');
Poiché i StylePropertyMap
sono oggetti simili a quelli di una mappa, supportano tutti i sospetti presenti (get/set/keys/values/entries), il che li rende flessibili per lavorare con:
// 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.
Tieni presente che, nel secondo esempio, opacity
è impostato sulla stringa ('0.3'
), ma viene restituito un numero quando la proprietà viene letta in un secondo momento.
Vantaggi
Quindi, quali problemi sta cercando di risolvere l'OM di tipo CSS? Osservando gli esempi precedenti (e nel resto di questo articolo), potresti sostenere che l'OM di tipo CSS è molto più dettagliato del precedente modello a oggetti. Sono d'accordo.
Prima di cancellare l'OM Type, considera alcune delle sue funzionalità principali:
Meno bug, ad esempio i valori numerici vengono sempre restituiti come numeri, non stringhe.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
Operazioni aritmetiche e conversione di unità. Converti tra unità di lunghezza assoluta (ad es.
px
->cm
) ed esegui calcoli matematici di base.Clamping e arrotondamento del valore. I valori dell'OM rotondo e/o fissandoli sono stati digitati in modo che rientrino negli intervalli accettabili per una proprietà.
Miglior rendimento. Il browser deve svolgere meno attività di serializzazione e deserializzazione dei valori stringa. Ora il motore utilizza una comprensione simile dei valori CSS per JS e C++. Tab Akins ha mostrato alcuni primi benchmark di rendimento che mettono circa il 30% più veloce dell'OM digitato in operazioni/sec rispetto all'utilizzo del vecchio CSSOM e delle vecchie stringhe. Ciò può essere significativo per le animazioni CSS rapide che utilizzano
requestionAnimationFrame()
. crbug.com/808933 monitora le prestazioni aggiuntive in Blink.Gestione degli errori. Nuovi metodi di analisi introducono la gestione degli errori nel mondo dei CSS.
"Devo utilizzare stringhe o nomi CSS con le maiuscole in cammello?" Non dovrai più chiederti se i nomi contengono lettere maiuscole o minuscole (ad es.
el.style.backgroundColor
oel.style['background-color']
). I nomi delle proprietà CSS nell'OM digitato sono sempre stringhe corrispondenti a ciò che scrivi effettivamente in CSS :)
Supporto del browser e rilevamento delle funzionalità
Il comando OM digitato è stato indirizzato a Chrome 66 e verrà implementato in Firefox. Edge ha mostrato indicazioni di supporto, ma deve ancora aggiungerlo alla dashboard della piattaforma.
Per il rilevamento delle funzionalità, puoi controllare se è definito uno dei fattori numerici CSS.*
:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Nozioni di base sulle API
Accesso agli stili
I valori sono separati dalle unità nell'OM di tipo CSS. Una volta ottenuto uno stile, viene restituito un valore CSSUnitValue
contenente value
e unit
:
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
Stili elaborati
Gli stili calcolati
sono stati spostati da un'API nel giorno window
a un nuovo metodo il giorno HTMLElement
,
computedStyleMap()
:
CSSOM precedente
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
Nuovo OM digitato
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Arrotondamento / clampaggio dei valori
Una delle caratteristiche interessanti del nuovo modello a oggetti è il blocco automatico e/o l'arrotondamento dei valori di stile calcolati. Ad esempio, supponiamo che tu provi a impostare
opacity
su un valore al di fuori dell'intervallo accettabile, [0, 1]. L'OM digitato blocca
il valore su 1
durante il calcolo dello stile:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
Allo stesso modo, l'impostazione di z-index:15.4
viene arrotondato a 15
in modo che il valore rimanga un numero intero.
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.
Valori numerici CSS
I numeri sono rappresentati da due tipi di oggetti CSSNumericValue
in OM digitato:
CSSUnitValue
: valori che contengono un solo tipo di unità (ad es."42px"
).CSSMathValue
: valori che contengono più di un valore/unità come un'espressione matematica (ad es."calc(56em + 10%)"
).
Valori unitari
I valori numerici semplici ("50%"
) sono rappresentati da oggetti CSSUnitValue
.
Anche se potresti creare questi oggetti direttamente (new CSSUnitValue(10, 'px')
), nella maggior parte dei casi utilizzerai i metodi di fabbrica 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'
Consulta le specifiche per l'elenco completo dei metodi CSS.*
.
Valori matematici
Gli oggetti CSSMathValue
rappresentano espressioni matematiche e in genere
contengono più di un valore/unità. L'esempio comune è la creazione di un'espressione calc()
CSS, ma esistono metodi per tutte le funzioni 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)"
Espressioni nidificate
L'utilizzo delle funzioni matematiche per creare valori più complessi crea un po' di confusione. Di seguito sono riportati alcuni esempi per iniziare. Ho aggiunto un ulteriore rientro per facilitare la lettura.
calc(1px - 2 * 3em)
verrebbe creato come:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
verrebbe creato come:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
verrebbe creato come:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Operazioni aritmetiche
Una delle funzionalità più utili dell'OM di tipo CSS è la possibilità di eseguire operazioni matematiche sugli oggetti CSSUnitValue
.
Operazioni di base
Le operazioni di base (add
/sub
/mul
/div
/min
/max
) sono supportate:
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))"
Conversione
Le unità di lunghezza assoluta possono essere convertite in altre unità di lunghezza:
// 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
Equality
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
Valori della trasformazione CSS
Le trasformazioni CSS vengono create con un valore CSSTransformValue
e passano un array di valori di trasformazione (ad es. CSSRotate
, CSScale
, CSSSkew
, CSSSkewX
,CSSSkewY
). Ad esempio, supponi di voler ricreare questo CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Tradotto in OM digitato:
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))
]);
Oltre alle Preferenze di lettura, CSSTransformValue
ha delle funzionalità
interessanti. Ha una proprietà booleana per distinguere le trasformazioni 2D e 3D
e un metodo .toMatrix()
per restituire la rappresentazione DOMMatrix
di una
trasformazione:
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
Esempio: animare un cubo
Vediamo un esempio pratico di utilizzo delle trasformazioni. Utilizzeremo le trasformazioni JavaScript e CSS per animare un cubo.
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.
})();
Tieni presente che:
- Con i valori numerici possiamo incrementare l'angolo direttamente con il calcolo matematico.
- Anziché modificare il DOM o leggere un valore su ogni frame (ad esempio, nessun
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
), l'animazione si basa sull'aggiornamento dell'oggetto datiCSSTransformValue
sottostante, migliorando le prestazioni.
Demo
Se il browser supporta la digitazione OM, sotto verrà visualizzato un cubo rosso. Il cubo inizia a ruotare quando ci passi il mouse sopra. L'animazione è gestita da CSS Typed OM! 🤘
Valori delle proprietà personalizzate CSS
CSS var()
diventa un oggetto CSSVariableReferenceValue
nell'OM digitato.
I valori vengono analizzati in CSSUnparsedValue
perché è possibile utilizzare qualsiasi tipo (px, %, em, rgba() e così via).
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'
Se vuoi ottenere il valore di una proprietà personalizzata, devi fare un po' di lavoro:
<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>
Valori di posizione
Le proprietà CSS che hanno una posizione x/y separata da spazi, come object-position
, sono rappresentate da oggetti 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
Analisi dei valori
L'OM Typed introduce metodi di analisi nella piattaforma web. Ciò significa che puoi finalmente analizzare i valori CSS in modo programmatico, prima di provare a utilizzarli. Questa nuova funzionalità è un potenziale risparmio di vita per l'individuazione dei primi bug e dei CSS con formato non valido.
Analizza uno stile completo:
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)'
Analizza i valori in CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Gestione degli errori
Esempio: controlla se l'analizzatore sintattico CSS sarà soddisfatto di questo valore transform
:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Conclusione
È bello finalmente disporre di un modello a oggetti aggiornato per CSS. Lavorare con gli archi non mi è mai sembrato giusto. L'API OM di tipo CSS è un po' dettagliata, ma si spera che comporti meno bug e un codice più performante in futuro.