Résumé
CSS dispose désormais d'une API basée sur les objets appropriée pour utiliser des valeurs en JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
Fini le temps où il fallait concaténer des chaînes et bugs subtils !
Introduction
Ancien code CSSOM
Les CSS possèdent un modèle d'objet (CSSOM) depuis de nombreuses années. Chaque fois que vous lisez ou définissez .style
en JavaScript, vous l'utilisez:
// 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;
Nouveau OM de type CSS
Le nouveau CSS Typed Object Model (OMD type), une partie de l'initiative Houdini, élargit cette vision du monde en ajoutant des types, des méthodes et un modèle d'objet approprié aux valeurs CSS. Au lieu de chaînes, les valeurs sont exposées en tant qu'objets JavaScript pour faciliter une manipulation performante (et raisonnable) du CSS.
Au lieu d'utiliser element.style
, vous accéderez aux styles via une nouvelle propriété .attributeStyleMap
pour les éléments et une propriété .styleMap
pour les règles de feuille de style. Les deux renvoient un objet 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');
Étant donné que les StylePropertyMap
sont des objets de type Map, ils sont compatibles avec tous les suspects habituels (get/set/keys/values/inputs), ce qui les rend flexibles pour leur utilisation:
// 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.
Notez que dans le deuxième exemple, opacity
est défini sur "string" ('0.3'
), mais un nombre s'affiche lorsque la propriété est lue ultérieurement.
Avantages
Quels problèmes l'OMS de type CSS tente-t-il de résoudre ? En examinant les exemples ci-dessus (et tout au long de cet article), vous pouvez considérer que la commande OM de type CSS est beaucoup plus détaillée que l'ancien modèle d'objet. Je suis d'accord !
Avant d'écrire l'OM typé, tenez compte de certaines des principales caractéristiques qu'il apporte:
Moins de bugs. Par exemple, les valeurs numériques sont toujours renvoyées sous forme de nombres et non de chaînes.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
Opérations arithmétiques et conversion d'unités : convertissez des unités de longueur absolue (par exemple,
px
->cm
) et effectuez des calculs de base.Limitation et arrondi de la valeur : Saisissez les arrondis et/ou limites OM afin qu'ils soient compris dans les plages acceptables pour une propriété.
Performances améliorées. Le navigateur doit réaliser moins de travail de sérialisation et de désérialisation des valeurs de chaîne. À présent, le moteur utilise une compréhension similaire des valeurs CSS en JavaScript et C++. Tab Akins a présenté quelques benchmarks de performances précoces qui ont permis d'effectuer environ 30% plus rapidement d'opérations par seconde qu'avec l'ancien CSSOM et les anciennes chaînes. Cela peut se révéler utile pour les animations CSS rapides utilisant
requestionAnimationFrame()
. crbug.com/808933 suit les tâches de performances supplémentaires dans Blink.Traitement des erreurs. De nouvelles méthodes d'analyse permettent de gérer les erreurs dans l'univers CSS.
"Dois-je utiliser des chaînes ou des noms CSS en Camel Case ("casse de chameau") ? Plus besoin de deviner si les noms sont en camelcase ou en chaînes (par exemple,
el.style.backgroundColor
ouel.style['background-color']
). Les noms de propriétés CSS dans l'OM typé sont toujours des chaînes, correspondant à ce que vous écrivez réellement en CSS :)
Prise en charge des navigateurs et détection des fonctionnalités
La commande OM a été saisie dans Chrome 66 et est en cours d'implémentation dans Firefox. Edge présente des signes de compatibilité, mais ne l'a pas encore ajouté à son tableau de bord de plate-forme.
Pour la détection de caractéristiques, vous pouvez vérifier si l'une des fabriques numériques CSS.*
est définie:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Principes de base des API
Accéder aux styles
Les valeurs sont distinctes des unités dans les blocs de type CSS OM. L'obtention d'un style renvoie un CSSUnitValue
contenant un value
et un 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
Styles calculés
Les styles calculés ont été déplacés d'une API sur window
vers une nouvelle méthode sur HTMLElement
, computedStyleMap()
:
Ancien code CSSOM
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
Nouvel OM saisi
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Limitation / Arrondi de la valeur
L'une des fonctionnalités intéressantes du nouveau modèle d'objet est la limitation et/ou l'arrondi automatiques des valeurs de style calculées. Par exemple, supposons que vous essayiez de définir opacity
sur une valeur en dehors de la plage acceptable, [0, 1]. Si vous saisissez OM, la valeur est réduite à 1
lors du calcul du style:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
De même, si vous définissez z-index:15.4
arrondit à 15
, la valeur reste un entier.
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.
Valeurs numériques CSS
Les nombres sont représentés par deux types d'objets CSSNumericValue
dans OM typé:
CSSUnitValue
: valeurs contenant un seul type d'unité (par exemple,"42px"
).CSSMathValue
: valeurs contenant plusieurs valeurs/unités, comme une expression mathématique (par exemple,"calc(56em + 10%)"
).
Valeurs unitaires
Les valeurs numériques simples ("50%"
) sont représentées par des objets CSSUnitValue
.
Bien que vous puissiez créer ces objets directement (new CSSUnitValue(10, 'px')
), vous utiliserez la plupart du temps les méthodes de fabrique 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'
Consultez la spécification pour obtenir la liste complète des méthodes CSS.*
.
Valeurs mathématiques
Les objets CSSMathValue
représentent des expressions mathématiques et contiennent généralement plusieurs valeurs/unités. L'exemple courant consiste à créer une expression CSS calc()
, mais il existe des méthodes pour toutes les fonctions CSS : calc()
, min()
et 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)"
Expressions imbriquées
L'utilisation des fonctions mathématiques pour créer des valeurs plus complexes peut prêter à confusion. Vous trouverez ci-dessous quelques exemples pour vous aider à démarrer. j'ai ajouté une mise en retrait supplémentaire pour les rendre plus faciles à lire.
calc(1px - 2 * 3em)
se présenterait comme suit:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
se présenterait comme suit:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
se présenterait comme suit:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Opérations arithmétiques
L'une des fonctionnalités les plus utiles de l'OM typé CSS vous permet d'effectuer des opérations mathématiques sur des objets CSSUnitValue
.
Opérations de base
Les opérations de base (add
/sub
/mul
/div
/min
/max
) sont acceptées:
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))"
Conversion
Les unités de longueur absolue peuvent être converties en d'autres longueurs d'unités:
// 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
Égalité
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
Valeurs de transformation CSS
Les transformations CSS sont créées avec un CSSTransformValue
et la transmission d'un tableau de valeurs de transformation (par exemple, CSSRotate
, CSScale
, CSSSkew
, CSSSkewX
, CSSSkewY
). Par exemple, imaginons que vous souhaitiez recréer ce CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Traduit en OM saisi:
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))
]);
En plus de la verbosité (lolz !), CSSTransformValue
présente des fonctionnalités intéressantes. Elle dispose d'une propriété booléenne permettant de différencier les transformations 2D et 3D, ainsi que d'une méthode .toMatrix()
pour renvoyer la représentation DOMMatrix
d'une transformation:
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
Exemple: animer un cube
Voyons un exemple pratique d'utilisation de transformations. Nous allons utiliser les transformations JavaScript et CSS pour animer un cube.
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.
})();
Remarque :
- Les valeurs numériques signifient que nous pouvons incrémenter l'angle directement à l'aide de calculs mathématiques !
- Plutôt que de toucher le DOM ou de lire une valeur sur chaque frame (par exemple, pas de
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
), l'animation est guidée en modifiant l'objet de donnéesCSSTransformValue
sous-jacent, ce qui améliore les performances.
Démonstration
Ci-dessous, un cube rouge s'affiche si votre navigateur prend en charge l'OM typé. Le cube commence à pivoter lorsque vous passez la souris dessus. L'animation est fournie par CSS Typed OM. 🤘
Valeurs des propriétés personnalisées CSS
Le CSS var()
devient un objet CSSVariableReferenceValue
dans l'OM typé.
Leurs valeurs sont analysées dans CSSUnparsedValue
, car elles peuvent prendre n'importe quel type (px, %, em, rgba(), etc.).
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'
Si vous souhaitez obtenir la valeur d'une propriété personnalisée, vous devez effectuer quelques opérations:
<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>
Valeurs de position
Les propriétés CSS qui acceptent une position x/y séparée par un espace, telles que object-position
, sont représentées par des objets 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
Analyser des valeurs
L'OM typé présente des méthodes d'analyse à la plate-forme Web. Cela signifie que vous pouvez enfin analyser les valeurs CSS de manière programmatique, avant d'essayer de les utiliser. Cette nouvelle fonctionnalité peut vous sauver la vie en identifiant les premiers bugs et les fichiers CSS mal formés.
Analyser un style complet:
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)'
Analysez les valeurs dans CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Gestion des exceptions
Exemple – Vérifiez si l'analyseur CSS est satisfait de la valeur transform
:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Conclusion
C'est bien d'avoir enfin un modèle d'objet mis à jour pour CSS. Travailler avec des cordes ne me m'a jamais plu. L'API CSS Typed OM est un peu détaillée, mais elle devrait réduire le nombre de bugs et améliorer les performances de code par la suite.