Houdini: Desmitificación de CSS

¿Alguna vez has pensado en la cantidad de trabajo que realiza el CSS? Si cambias un solo atributo, todo el sitio web aparecerá con un diseño diferente de repente. Es como de magia. Hasta ahora, nosotros (la comunidad de desarrolladores web) solo hemos podido presenciar y observar la magia. ¿Qué pasa si queremos idear nuestra propia magia? ¿Y si queremos convertirnos en magos?

¡Ingresa a Houdini!

El grupo de trabajo de Houdini está formado por ingenieros de Mozilla, Apple, Opera, Microsoft, HP, Intel y Google que trabajan juntos para exponer ciertas partes del motor de CSS a los desarrolladores web. El grupo de trabajo está trabajando en una colección de borradores con el objetivo de que el W3C los acepte para que se conviertan en estándares web reales. Se establecieron algunos objetivos de alto nivel y los convirtieron en borradores de especificaciones que, a su vez, dieron lugar a un conjunto de borradores de especificaciones de nivel inferior complementario.

La colección de estos borradores es lo que generalmente se entiende cuando alguien habla de "Houdini". Al momento de la redacción, la lista de borradores está incompleta y algunos de ellos son meros marcadores de posición.

Especificaciones

Worklets (spec)

Los Worklets por sí solos no son muy útiles. Son un concepto introducido para posibilitar muchos de los borradores posteriores. Si pensaste en Web Workers cuando leíste "worklet", no te equivocaste. Se superponen mucho conceptualmente. Entonces, ¿por qué algo nuevo cuando ya tenemos trabajadores?

El objetivo de Houdini es exponer nuevas APIs para permitir que los desarrolladores web vinculen su propio código al motor de CSS y a los sistemas que lo rodean. Probablemente no sea realista suponer que algunos de estos fragmentos de código tendrán que ejecutarse todos. single. frame. Algunos tienen que hacerlo por definición. Entre comillas las especificaciones de Web Worker:

Eso significa que los trabajadores web no son viables para lo que Houdini planea hacer. Por lo tanto, se inventaron los worklets. Los Worklets usan clases ES2015 para definir una colección de métodos, cuyas firmas están predefinidas por el tipo de worklet. Son ligeros y de corta duración.

API de CSS Paint (spec)

La API de Paint está habilitada de forma predeterminada en Chrome 65. Lee la introducción detallada.

Worklet del compositor

La API que se describe aquí es obsoleta. Se rediseñó el worklet del compositor y ahora se propone como "Worklet de animación". Obtén más información sobre la iteración actual de la API.

Aunque la especificación del worklet del compositor se movió a WICG y se repetirá, es la que más me entusiasma. El motor CSS subcontrata algunas operaciones a la tarjeta gráfica de tu computadora, aunque esto depende tanto de la tarjeta gráfica como del dispositivo en general.

Un navegador generalmente toma el árbol del DOM y, según criterios específicos, decide asignar su propia capa a algunas ramas y subárboles. Estos subárboles se pintan solos sobre él (quizá con un worklet de pintura en el futuro). Como paso final, todas estas capas individuales, ahora pintadas, se apilan y posicionan una encima de la otra, respetando los índices z, las transformaciones 3D y así, para generar la imagen final que se puede ver en la pantalla. Este proceso se denomina composición y lo ejecuta el compositor.

La ventaja del proceso de composición es que no tienes que hacer que todos los elementos vuelvan a pintarse cuando la página se desplaza un poco. En su lugar, puedes volver a usar las capas del fotograma anterior y volver a ejecutar el compositor con la posición de desplazamiento actualizada. Esto agiliza el proceso. Esto nos ayuda a alcanzar los 60 fps.

Trabajador del compositor.

Como su nombre lo indica, el worklet del compositor te permite conectarte con él e influir en la forma en que la capa de un elemento, que ya se pintó, se posiciona y se superpone a las demás capas.

Para ser un poco más específicos, puedes indicarle al navegador que deseas conectarte al proceso de composición de un nodo del DOM determinado y que puedes solicitar acceso a ciertos atributos, como la posición de desplazamiento, transform o opacity. Esto fuerza este elemento a su propia capa y, en cada fotograma, se llama a tu código. Para mover tu capa, puedes manipular la transformación de las capas y cambiar sus atributos (como opacity), lo que te permite hacer cosas complejas y sofisticadas a unos 60 FPS.

Esta es una implementación completa del desplazamiento con paralaje, mediante el worklet del 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 escribió un polyfill para el worklet del compositor para que lo pruebes, obviamente, con un impacto en el rendimiento mucho mayor.

Worklet de diseño (spec)

Se propuso el primer borrador de especificaciones reales. La implementación es un buen momento.

Una vez más, la especificación para esto está prácticamente vacía, pero el concepto es intrigante: escribe tu propio diseño. El worklet de diseño te permite ejecutar display: layout('myLayout') y ejecutar JavaScript para organizar los elementos secundarios de un nodo en el cuadro del nodo.

Por supuesto, ejecutar una implementación completa de JavaScript del diseño flex-box de CSS es más lento que ejecutar una implementación nativa equivalente, pero es fácil imaginar una situación en la que los recortes pueden generar una mejora en el rendimiento. Imagina un sitio web que solo contenga mosaicos, como Windows 10 o un diseño de estilo de albañilería. No se usa la posición absoluta ni fija, ni z-index, y los elementos nunca se superponen o tienen algún tipo de borde o desbordamiento. Poder omitir todas estas verificaciones en el rediseño podría mejorar el rendimiento.

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 escrito (spec)

El CSSOM escrito (Modelo de objetos de CSS o modelo de objetos de Hojas de estilo en cascada) soluciona un problema que probablemente todos nos hemos encontrado y que aprendimos a resolver. Lo ilustraré con una línea de JavaScript:

    $('#someDiv').style.height = getRandomInt() + 'px';

Hacemos cálculos matemáticos: convertimos un número en una cadena para agregar una unidad solo para que el navegador analice esa cadena y la convierta de nuevo en un número para el motor de CSS. Esto se vuelve aún más feo cuando manipulas las transformaciones con JavaScript. No más. CSS está a punto de escribir.

Este borrador es uno de los más antiguos y ya se está trabajando en un polyfill. (Renuncia de responsabilidad: Por supuesto, usar polyfill agregará aún más sobrecarga de procesamiento. El punto es mostrar qué tan conveniente es la API).

En lugar de cadenas, trabajarás en el StylePropertyMap de un elemento, en el que cada atributo CSS tiene su propia clave y su tipo de valor correspondiente. Los atributos como width tienen LengthValue como tipo de valor. Un LengthValue es un diccionario de todas las unidades de CSS, como em, rem, px y percent, entre otras. La configuración de height: calc(5px + 5%) produciría una LengthValue{px: 5, percent: 5}. Algunas propiedades, como box-sizing, solo aceptan ciertas palabras clave y, por lo tanto, tienen un tipo de valor KeywordValue. La validez de esos atributos podría verificarse durante el tiempo de ejecución.

<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}

Propiedades y valores

(spec)

¿Conoces las propiedades personalizadas de CSS (o sus alias no oficiales, “variables de CSS”)? Estas son ellas, pero con tipos. Hasta ahora, las variables solo podían tener valores de cadena y usar un enfoque simple de búsqueda y reemplazo. Este borrador te permitiría no solo especificar un tipo para tus variables, sino también definir un valor predeterminado e influir en el comportamiento de herencia con una API de JavaScript. Técnicamente, esto también permitiría que las propiedades personalizadas se animen con transiciones y animaciones de CSS estándar, que también se está considerando.

["--scale-x", "--scale-y"].forEach(function(name) {
document.registerProperty({
    name: name,
    syntax: "<number>",
    inherits: false,
    initialValue: "1"
    });
});

Métricas de fuente

Las métricas de fuente son exactamente lo que parece. ¿Qué es el cuadro de límite (o los cuadros de límite) cuando renderiza la string X con la fuente Y en el tamaño Z? ¿Qué sucede si uso anotaciones de Ruby? Esto se pidió mucho y Houdini finalmente debería hacer realidad esos deseos.

Espera. Hay más calcomanías.

Hay aún más especificaciones en la lista de borradores de Houdini, pero el futuro de estas es bastante incierto y no son mucho más que marcadores de posición para las ideas. Algunos ejemplos incluyen comportamientos personalizados de desbordamiento, la API de extensión de sintaxis CSS, la extensión del comportamiento nativo de desplazamiento y otros elementos similares ambiciosos, los cuales permiten hacer acciones en la plataforma web que antes no eran posibles.

Demostraciones

Ya configuré de código abierto el código para la demostración (demostración en vivo con polyfill).