Texto longo, leia o resumo
Agora o CSS tem uma API adequada baseada em objetos para trabalhar com valores em JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
Os dias de concatenar strings e bugs sutis acabaram!
Introdução
CSSOM antigo
O CSS tem um modelo de objeto (CSSOM) há muitos anos. Na verdade, sempre que você lê/define .style em JavaScript, está usando o recurso:
// 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;
Nova OM tipada do CSS
O novo CSS Typed Object Model (Typed OM), parte do esforço do Houdini, amplia essa visão de mundo adicionando tipos, métodos e um modelo de objeto adequado aos valores CSS. Em vez de strings, os valores são expostos como objetos JavaScript para facilitar a manipulação eficiente (e sensata) de CSS.
Em vez de usar element.style, você vai acessar os estilos com uma nova propriedade .attributeStyleMap para elementos e uma propriedade .styleMap para regras de folha de estilo. Ambos retornam um 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');
Como StylePropertyMaps são objetos semelhantes a mapas, eles são compatíveis com todos os suspeitos comuns (get/set/keys/values/entries), o que os torna flexíveis para trabalhar:
// 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.
No segundo exemplo, opacity está definido como string ('0.3'), mas um número é retornado quando a propriedade é lida novamente mais tarde.
Vantagens
Então, quais problemas a OM tipada do CSS está tentando resolver? Analisando os exemplos acima (e ao longo do restante deste artigo), você pode argumentar que o CSS Typed OM é muito mais detalhado do que o modelo de objeto antigo. Concordo!
Antes de descartar o Typed OM, considere alguns dos principais recursos que ele oferece:
Menos bugs. Por exemplo, os valores numéricos são sempre retornados como números, não como strings.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!Operações aritméticas e conversão de unidades. Converta entre unidades de comprimento absoluto (por exemplo,
px->cm) e faça cálculos básicos.Fixação e arredondamento de valores. A OM tipada arredonda e/ou limita valores para que eles fiquem dentro dos intervalos aceitáveis de uma propriedade.
Melhor performance. O navegador precisa fazer menos trabalho serializando e desserializando valores de string. Agora, o mecanismo usa uma compreensão semelhante de valores CSS em JS e C++. Tab Atkins mostrou alguns benchmarks de desempenho iniciais que colocam o Typed OM em ~30% mais rápido em operações/segundo quando comparado ao uso do CSSOM e das strings antigos. Isso pode ser significativo para animações CSS rápidas usando
requestionAnimationFrame(). crbug.com/808933 acompanha trabalhos adicionais de desempenho no Blink.Tratamento de erros. Os novos métodos de análise trazem tratamento de erros para o mundo do CSS.
"Devo usar nomes ou strings CSS em camel case?" Não é mais necessário adivinhar se os nomes estão em camelCase ou são strings (por exemplo,
el.style.backgroundColorxel.style['background-color']). Os nomes de propriedades CSS no Typed OM são sempre strings, correspondendo ao que você realmente escreve em CSS :)
Suporte ao navegador e detecção de recursos
O Typed OM foi lançado no Chrome 66 e está sendo implementado no Firefox. O Edge mostrou sinais de suporte, mas ainda não adicionou o recurso ao painel da plataforma.
Para detecção de recursos, verifique se uma das fábricas numéricas CSS.* está definida:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Princípios básicos da API
Como acessar estilos
Os valores são separados das unidades no modelo de objeto tipado do CSS. Ao receber um estilo, você recebe um
CSSUnitValue que contém um value e um 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
Os estilos calculados
foram movidos de uma API em window para um novo método em HTMLElement,
computedStyleMap():
CSSOM antigo
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
Nova OM tipada
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Limitação / arredondamento de valor
Um dos recursos interessantes do novo modelo de objeto é o ajuste e/ou
arredondamento automático dos valores de estilo calculados. Por exemplo, digamos que você tente definir opacity como um valor fora do intervalo aceitável, [0, 1]. OM tipado fixa o valor em 1 ao calcular o 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.
Da mesma forma, definir z-index:15.4 arredonda para 15, para que o valor permaneça um número inteiro.
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
Os números são representados por dois tipos de objetos CSSNumericValue no Typed OM:
CSSUnitValue: valores que contêm um único tipo de unidade (por exemplo,"42px").CSSMathValue: valores que contêm mais de um valor/unidade, como uma expressão matemática (por exemplo,"calc(56em + 10%)").
Valores de unidade
Valores numéricos simples ("50%") são representados por objetos CSSUnitValue.
Embora você possa criar esses objetos diretamente (new CSSUnitValue(10, 'px'))
, na maioria das vezes, você vai usar os 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'
Consulte a especificação para a lista completa
de métodos CSS.*.
Valores matemáticos
Os objetos CSSMathValue representam expressões matemáticas e geralmente contêm mais de um valor/unidade. O exemplo comum é criar uma expressão calc()
de CSS, mas há métodos para todas as funções de 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)"
Expressões aninhadas
Usar as funções matemáticas para criar valores mais complexos fica um pouco confuso. Confira alguns exemplos para começar. Adicionei um recuo extra para facilitar a leitura.
calc(1px - 2 * 3em) seria construído da seguinte maneira:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px) seria construído da seguinte maneira:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px) seria construído da seguinte maneira:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Operações aritméticas
Um dos recursos mais úteis do CSS Typed OM é a possibilidade de realizar operações matemáticas em objetos CSSUnitValue.
Operações básicas
As operações básicas (add/sub/mul/div/min/max) são aceitas:
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))"
Conversão
Unidades de comprimento absoluto podem ser convertidas em outros comprimentos de unidade:
// 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
Valores de transformação do CSS
As transformações de CSS são criadas com um CSSTransformValue e transmitindo uma matriz de valores de transformação (por exemplo, CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). Por exemplo, digamos que você queira recriar este CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Traduzido para OM digitada:
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))
]);
Além da verbosidade (lolz!), O CSSTransformValue tem alguns recursos
legais. Ele tem uma propriedade booleana para diferenciar transformações 2D e 3D
e um método .toMatrix() para retornar a representação DOMMatrix de uma
transformação:
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
Exemplo: animação de um cubo
Vamos ver um exemplo prático de como usar transformações. Vamos usar JavaScript e transformações CSS para animar um 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.
})();
Observe que:
- Valores numéricos significam que podemos incrementar o ângulo diretamente usando matemática.
- Em vez de tocar no DOM ou ler um valor em cada frame (por exemplo, sem
box.style.transform=`rotate(0,0,1,${newAngle}deg)`), a animação é impulsionada por atualizar o objeto de dadosCSSTransformValuesubjacente, melhorando o desempenho.
Demonstração
Abaixo, você vai ver um cubo vermelho se o navegador for compatível com o Typed OM. O cubo começa a girar quando você passa o cursor sobre ele. A animação é feita com o CSS Typed OM. 🤘
Valores de propriedades personalizadas de CSS
Os var() do CSS se tornam um objeto CSSVariableReferenceValue no Typed OM.
Os valores são analisados em CSSUnparsedValue porque podem ter qualquer tipo (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'
Se você quiser extrair o valor de uma propriedade personalizada, vai precisar fazer um pouco de trabalho:
<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 posição
Propriedades CSS que usam uma posição x/y separada por espaços, como
object-position, são representadas por 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
Como analisar valores
A OM tipada apresenta métodos de análise à plataforma da Web. Isso significa que você pode finalmente analisar valores de CSS de forma programática, antes de tentar usá-los. Esse novo recurso pode ser muito útil para detectar bugs iniciais e CSS malformado.
Analisar um 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)'
Analise os valores em CSSUnitValue:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Tratamento de erros
Exemplo: verifique se o analisador CSS vai aceitar este valor transform:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Conclusão
É bom finalmente ter um modelo de objeto atualizado para CSS. Trabalhar com strings nunca me pareceu certo. A API CSS Typed OM é um pouco detalhada, mas esperamos que ela resulte em menos bugs e um código mais eficiente no futuro.