Resumen
CSS ahora tiene una API basada en objetos adecuada para trabajar con valores en JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
Se acabaron los días de concatenación de cadenas y los errores sutiles.
Introducción
CSSOM anterior
CSS tiene un modelo de objetos (CSSOM) durante muchos años. De hecho, cada vez que leas o establezcas .style
en JavaScript, lo usarás:
// 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;
Nuevo OM escrito en CSS
El nuevo Modelo de objetos escritos de CSS (OM escrito), parte del esfuerzo de Houdini, amplía esta visión del mundo agregando tipos, métodos y un modelo de objetos adecuado a los valores de CSS. En lugar de cadenas, los valores se exponen como objetos de JavaScript para facilitar una manipulación de CSS de alto rendimiento (y sensata).
En lugar de usar element.style
, accederás a los estilos a través de una nueva propiedad .attributeStyleMap
para los elementos y una propiedad .styleMap
para las reglas de la hoja de estilo. Ambos muestran un objeto 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');
Debido a que las StylePropertyMap
son objetos similares a un mapa, admiten todos los sospechosos habituales (get/set/keys/values/ingress), lo que las hace flexibles para trabajar con lo siguiente:
// 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.
Ten en cuenta que, en el segundo ejemplo, opacity
se establece como una cadena ('0.3'
), pero se muestra un número cuando la propiedad se vuelve a leer más adelante.
Beneficios
Entonces, ¿qué problemas está tratando de resolver el OM escrito en CSS? Si miras los ejemplos anteriores (y en el resto de este artículo), podrías pensar que el OM escrito en CSS es mucho más detallado que el modelo de objetos anterior. ¡Estoy de acuerdo!
Antes de cancelar OM escrito, considera algunas de las características clave que aporta a la tabla:
Menos errores. P.ej., los valores numéricos siempre se muestran como números, no como strings.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
Operaciones aritméticas y conversión de unidades: Convierte unidades de longitud absoluta (p. ej.,
px
->cm
) y haz matemáticas básicas.Restricción y redondeo de valores. Valores de redondeos o restricciones de OM escritos para que estén dentro de los rangos aceptables para una propiedad
Mejor rendimiento. El navegador debe realizar menos trabajo de serializar y deserializar los valores de la string. Ahora, el motor utiliza una comprensión similar de los valores de CSS en JS y C++. Tab Akins mostró algunas comparativas de rendimiento iniciales que colocan OM en ~30% más rápido en operaciones por segundo en comparación con el uso del CSSOM y las cadenas anteriores. Esto puede ser significativo para animaciones de CSS rápidas que usan
requestionAnimationFrame()
. crbug.com/808933 realiza un seguimiento del trabajo de rendimiento adicional en Blink.Manejo de errores. Los nuevos métodos de análisis incorporan el manejo de errores en el mundo de CSS.
"¿Debo usar cadenas o nombres CSS en mayúsculas y minúsculas?" Ya no tienes que adivinar si los nombres tienen mayúsculas mediales o cadenas (p. ej.,
el.style.backgroundColor
frente ael.style['background-color']
). Los nombres de las propiedades de CSS en Typed OM siempre son cadenas que coinciden con lo que realmente escribes en CSS :).
Detección de funciones y compatibilidad con navegadores
El OM escrito llegó a Chrome 66 y se implementará en Firefox. Edge mostró signos de compatibilidad, pero aún no lo agregó a su panel de la plataforma.
Para la detección de características, puedes verificar si se definió una de las fábricas numéricas CSS.*
:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Conceptos básicos de API
Cómo acceder a los estilos
Los valores son independientes de las unidades en OM escrito en CSS. Si obtienes un diseño, se muestra un CSSUnitValue
que contiene un value
y 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
Estilos calculados
Los estilos calculados pasaron de una API en window
a un método nuevo en HTMLElement
, computedStyleMap()
:
CSSOM anterior
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
Nuevo OM escrito
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Restricción o redondeo de valores
Una de las ventajas del nuevo modelo de objetos es la fijación o el redondeo automáticos de los valores de estilo calculados. A modo de ejemplo, supongamos que intentas establecer opacity
en un valor fuera del rango aceptable, [0, 1]. El OM escrito restringe el valor a 1
cuando se calcula el estilo:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
Del mismo modo, la configuración de z-index:15.4
se redondea a 15
, por lo que el valor sigue siendo un número entero.
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.
Valores numéricos de CSS
Los números se representan con dos tipos de objetos CSSNumericValue
en Typed OM:
CSSUnitValue
: Valores que contienen un solo tipo de unidad (p.ej.,"42px"
).CSSMathValue
: Valores que contienen más de un valor o unidad, como una expresión matemática (p.ej.,"calc(56em + 10%)"
).
Valores unitarios
Los valores numéricos simples ("50%"
) se representan con objetos CSSUnitValue
.
Si bien podrías crear estos objetos directamente (new CSSUnitValue(10, 'px')
), la mayoría de las veces usarás los métodos de fábrica 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 las especificaciones para ver la lista completa de métodos CSS.*
.
Valores matemáticos
Los objetos CSSMathValue
representan expresiones matemáticas y suelen contener más de un valor por unidad. El ejemplo común es crear una expresión calc()
de CSS, pero existen métodos para todas las funciones de CSS: calc()
, min()
y 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)"
Expresiones anidadas
El uso de las funciones matemáticas para crear valores más complejos se vuelve un poco confuso. Estos son algunos ejemplos que pueden servirte para comenzar. agregué sangría adicional para que sean más fáciles de leer.
calc(1px - 2 * 3em)
se construiría de la siguiente manera:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
se construiría de la siguiente manera:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
se construiría de la siguiente manera:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Operaciones aritméticas
Una de las funciones más útiles del OM escrito de CSS es que puedes realizar operaciones matemáticas en objetos CSSUnitValue
.
Operaciones básicas
Se admiten las operaciones básicas (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))"
Conversión
Las unidades de longitud absoluta se pueden convertir en otras longitudes de unidad de la siguiente manera:
// 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
Igualdad
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
Valores de transformación de CSS
Las transformaciones de CSS se crean con una CSSTransformValue
y pasan un array de valores de transformación (p.ej., CSSRotate
, CSScale
, CSSSkew
, CSSSkewX
, CSSSkewY
). Por ejemplo, supongamos que deseas volver a crear esta CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Traducido al OM escrito:
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))
]);
Además de su verbosidad (¡lolz!), CSSTransformValue
tiene algunas funciones interesantes. Tiene una propiedad booleana para diferenciar las transformaciones 2D de las 3D, y un método .toMatrix()
para mostrar la representación DOMMatrix
de una transformación:
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
Ejemplo: Cómo animar un cubo
Veamos un ejemplo práctico del uso de transformaciones. Usaremos transformaciones de JavaScript y CSS para animar 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.
})();
Observa lo siguiente:
- ¡Los valores numéricos significan que podemos aumentar el ángulo directamente usando matemáticas!
- En lugar de tocar el DOM o leer un valor en cada fotograma (p.ej., sin
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
), la animación se impulsa mediante la actualización del objeto de datosCSSTransformValue
subyacente, lo que mejora el rendimiento.
Demostración
A continuación, verás un cubo rojo si tu navegador es compatible con Typed OM. El cubo comienza a girar cuando pasas el mouse sobre él. La animación funciona con CSS Typed OM. 🤘
Valores de las propiedades personalizadas de CSS
var()
de CSS se convierte en un objeto CSSVariableReferenceValue
en Typed OM.
Sus valores se analizan en CSSUnparsedValue
porque pueden tomar cualquier tipo (px, %, em, rgba(), etcétera).
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 deseas obtener el valor de una propiedad personalizada, debes realizar algunas tareas:
<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>
Valores de posición
Las propiedades de CSS que toman una posición x/y separada por espacios, como object-position
, se representan con objetos 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
Análisis de valores
El OM escrito presenta métodos de análisis en la plataforma web. Esto significa que puedes analizar los valores de CSS de manera programática, antes de intentar usarlos. Esta nueva función es un posible ahorro de vida si se detectan errores tempranos y CSS con errores de formato.
Analiza un estilo 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)'
Analiza los valores en CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Manejo de errores
Ejemplo: Comprueba si el analizador de CSS estará conforme con este valor de transform
:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Conclusión
Por fin, es bueno tener un modelo de objetos actualizado para CSS. Trabajar con cadenas nunca me sentía bien. La API de OM Typed de CSS es un poco detallada, pero esperamos que genere menos errores y un código con mejor rendimiento más adelante.