Você já pensou na quantidade de trabalho que o CSS faz? Você muda um único atributo e, de repente, todo o site aparece em um layout diferente. É uma espécie de magia. Até agora, nós, a comunidade de desenvolvedores da Web, só pudemos testemunhar e observar a magia. E se quisermos criar nossa própria magia? E se quisermos ser o mágico?
Houdini!
O grupo de trabalho do Houdini consiste em engenheiros da Mozilla, Apple, Opera, Microsoft, HP, Intel e Google trabalhando juntos para expor determinadas partes do motor CSS a desenvolvedores da Web. A força-tarefa está trabalhando em uma coleção de rascunhos com o objetivo de que eles sejam aceitos pelo W3C para se tornarem padrões da Web. Eles definiram algumas metas de alto nível, as transformaram em esboços de especificação, que, por sua vez, deram origem a um conjunto de esboços de especificação de suporte de nível inferior.
A coleta desses rascunhos é o que geralmente é mencionado quando alguém fala sobre "Houdini". No momento em que este artigo foi escrito, a lista de rascunhos estava incompleta, e alguns dos rascunhos eram meros marcadores de posição.
As especificações
Worklets (spec)
Os worklets por si só não são muito úteis. Eles são um conceito introduzido para tornar muitos dos rascunhos posteriores possíveis. Se você pensou em workers da Web ao ler "worklet", não está errado. Eles têm muitas sobreposições conceituais. Então, por que uma coisa nova quando já temos trabalhadores?
O objetivo do Houdini é expor novas APIs para que os desenvolvedores da Web conectem o próprio código ao mecanismo CSS e aos sistemas relacionados. Provavelmente não é irrealístico supor que alguns desses fragmentos de código precisarão ser executados em cada frame. Alguns deles precisam por definição. Citação da especificação do worker da Web:
Isso significa que os workers da Web não são viáveis para as coisas que o Houdini planeja fazer. Portanto, os worklets foram inventados. Os worklets usam classes ES2015 para definir uma coleção de métodos, com assinaturas predefinidas pelo tipo do worklet. Eles são leves e de curta duração.
API CSS Paint (especificação)
A API Paint é ativada por padrão no Chrome 65. Leia a introdução detalhada.
Worklet do compositor
A API descrita aqui está obsoleta. O worklet do compositor foi redesenhado e agora é proposto como "worklet de animação". Leia mais sobre a iteração atual da API.
Embora a especificação do worklet do compositor tenha sido movida para o WICG e será iterada, é a especificação que mais me empolga. Algumas operações são terceirizadas para a placa de vídeo do computador pelo mecanismo CSS, embora isso dependa da placa de vídeo e do dispositivo em geral.
Um navegador geralmente pega a árvore DOM e, com base em critérios específicos, decide dar a algumas ramificações e subárvores a própria camada. Esses subárvores são pintadas nela (talvez usando um worklet de pintura no futuro). Como etapa final, todas essas camadas individuais, agora pintadas, são empilhadas e posicionadas uma sobre a outra, respeitando os índices z, transformações 3D e assim por diante, para gerar a imagem final que fica visível na tela. Esse processo é chamado de composição e é executado pelo compositor.
A vantagem do processo de composição é que você não precisa fazer com que todos os elementos sejam pintados novamente quando a página rola um pouco. Em vez disso, é possível reutilizar as camadas do frame anterior e executar o compositor novamente com a posição de rolagem atualizada. Isso acelera as coisas. Isso nos ajuda a alcançar 60 fps.
Como o nome sugere, o worklet do compositor permite que você se conecte ao compositor e influencie a forma como a camada de um elemento, que já foi pintada, é posicionada e disposta sobre as outras camadas.
Para ser um pouco mais
específico, é possível informar ao navegador que você quer se conectar ao processo de composição
de um determinado nó do DOM e solicitar acesso a determinados atributos, como
posição de rolagem, transform
ou opacity
. Isso força esse elemento na própria
camada e em cada frame, seu código é chamado. Você pode mover a camada
manipulando a transformação de camadas e mudando os atributos dela (como opacity
),
permitindo que você faça coisas legais a 60 fps.
Confira uma implementação completa de rolagem de paralaxe usando o worklet do compositor.
// main.js
window.compositorWorklet.import('worklet.js')
.then(function() {
var animator = new CompositorAnimator('parallax');
animator.postMessage([
new CompositorProxy($('.scroller'), ['scrollTop']),
new CompositorProxy($('.parallax'), ['transform']),
]);
});
// worklet.js
registerCompositorAnimator('parallax', class {
tick(timestamp) {
var t = self.parallax.transform;
t.m42 = -0.1 * self.scroller.scrollTop;
self.parallax.transform = t;
}
onmessage(e) {
self.scroller = e.data[0];
self.parallax = e.data[1];
};
});
Robert Flack escreveu um polyfill para o worklet do compositor. Você pode testá-lo, obviamente com um impacto de desempenho muito maior.
Worklet de layout (especificação)
O primeiro rascunho de especificação real foi proposto. A implementação está longe de acontecer.
Novamente, a especificação para isso está praticamente vazia, mas o conceito é
intrigante: escreva seu próprio layout! O worklet de layout permite
fazer display: layout('myLayout')
e executar o JavaScript para organizar os filhos
de um nó na caixa dele.
É claro que executar uma implementação completa do JavaScript do layout flex-box
do CSS
é mais lento do que executar uma implementação nativa equivalente, mas é fácil
imaginar um cenário em que atalhos podem gerar um ganho de desempenho. Imagine um
site que consiste apenas em blocos, como o Windows 10 ou um layout
de estilo de alvenaria. A posição absoluta e fixa não é usada, nem z-index
, nem
os elementos se sobrepõem ou têm qualquer tipo de borda ou overflow. A capacidade de pular
todas essas verificações no redimensionamento pode resultar em um ganho de desempenho.
registerLayout('random-layout', class {
static get inputProperties() {
return [];
}
static get childrenInputProperties() {
return [];
}
layout(children, constraintSpace, styleMap) {
const width = constraintSpace.width;
const height = constraintSpace.height;
for (let child of children) {
const x = Math.random()*width;
const y = Math.random()*height;
const constraintSubSpace = new ConstraintSpace();
constraintSubSpace.width = width-x;
constraintSubSpace.height = height-y;
const childFragment = child.doLayout(constraintSubSpace);
childFragment.x = x;
childFragment.y = y;
}
return {
minContent: 0,
maxContent: 0,
width: width,
height: height,
fragments: [],
unPositionedChildren: [],
breakToken: null
};
}
});
CSSOM digitado (especificação)
O CSSOM tipado (modelo de objetos do CSS ou modelo de objetos de folhas de estilo em cascata) aborda um problema que provavelmente todos nós já encontramos e aprendemos a lidar. Vamos ilustrar com uma linha de JavaScript:
$('#someDiv').style.height = getRandomInt() + 'px';
Estamos fazendo cálculos, convertendo um número em uma string para anexar uma unidade apenas para que o navegador analise essa string e a converta de volta em um número para o mecanismo de CSS. Isso fica ainda mais feio quando você manipula transformações com JavaScript. Não mais. O CSS está prestes a digitar.
Este rascunho é um dos mais maduros, e um polyfill já está sendo trabalhado. (Isenção de responsabilidade: o uso do polyfill obviamente adiciona ainda mais sobrecarga computacional. O objetivo é mostrar como a API é conveniente.)
Em vez de strings, você vai trabalhar com o StylePropertyMap
de um elemento, em que
cada atributo CSS tem a própria chave e o tipo de valor correspondente. Atributos
como width
têm LengthValue
como tipo de valor. Um LengthValue
é um
dicionário de todas as unidades CSS, como em
, rem
, px
, percent
e assim por diante. Definir
height: calc(5px + 5%)
vai gerar um LengthValue{px: 5, percent: 5}
. Algumas
propriedades, como box-sizing
, aceitam apenas determinadas palavras-chave e, portanto, têm um
tipo de valor KeywordValue
. A validade desses atributos pode ser verificada
no momento da execução.
<div style="width: 200px;" id="div1"></div>
<div style="width: 300px;" id="div2"></div>
<div id="div3"></div>
<div style="margin-left: calc(5em + 50%);" id="div4"></div>
var w1 = $('#div1').styleMap.get('width');
var w2 = $('#div2').styleMap.get('width');
$('#div3').styleMap.set('background-size',
[new SimpleLength(200, 'px'), w1.add(w2)])
$('#div4')).styleMap.get('margin-left')
// => {em: 5, percent: 50}
Propriedades e valores
Você conhece as propriedades CSS personalizadas (ou o alias não oficial "variáveis CSS")? Elas são iguais, mas com tipos. Até agora, as variáveis só podiam ter valores de string e usavam uma abordagem simples de pesquisa e substituição. Esse rascunho permitiria não apenas especificar um tipo para suas variáveis, mas também definir um valor padrão e influenciar o comportamento de herança usando uma API JavaScript. Tecnicamente, isso também permitiria que propriedades personalizadas fossem animadas com transições e animações CSS padrão, o que também está sendo considerado.
["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
name: name,
syntax: "<number>",
inherits: false,
initialValue: "1"
});
});
Métricas de fonte
Métricas de fonte é exatamente o que parece. Qual é a caixa delimitadora (ou as caixas delimitadoras) quando renderizo a string X com a fonte Y no tamanho Z? E se eu usar anotações em Ruby? Isso foi muito solicitado, e o Houdini finalmente vai realizar esses desejos.
Mas não é só isso.
Há ainda mais especificações na lista de rascunhos do Houdini, mas o futuro delas é bastante incerto e elas não são muito mais do que marcadores de posição para ideias. Os exemplos incluem comportamentos de overflow personalizados, API de extensão de sintaxe CSS, extensão do comportamento de rolagem nativa e recursos ambiciosos semelhantes, que permitem coisas na plataforma da Web que não eram possíveis antes.
Demonstrações
O código da demonstração (demonstração ao vivo usando polyfill) está aberto.