Houdini: Desmitificación de CSS

¿Alguna vez pensaste en la cantidad de trabajo que realiza CSS? Cambias un solo atributo y, de repente, todo el sitio web aparece en un diseño diferente. Es algo así como magia. Hasta ahora, nosotros, la comunidad de desarrolladores web, solo hemos podido presenciar y observar la magia. ¿Y si queremos encontrar nuestra propia magia? ¿Y si queremos convertirnos en mago?

¡Conoce a Houdini!

El grupo de trabajo de Houdini está compuesto por ingenieros de Mozilla, Apple, Opera, Microsoft, HP, Intel y Google que trabajan juntos para exponer ciertas partes del motor CSS a los desarrolladores web. El grupo de trabajo está trabajando en una recopilación de borradores con el objetivo de que 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 origen a un conjunto de borradores de especificaciones de asistencia y de bajo nivel.

Cuando alguien habla de "Houdini", se suele decir que hay una colección de estos borradores. Al momento de la redacción, la lista de borradores está incompleta y algunas de ellas son meros marcadores de posición.

Las especificaciones

Worklets (spec)

Los Worklets por sí solos no son muy útiles. Es un concepto que se introdujo para hacer posibles muchos de los borradores posteriores. Si pensaste en los trabajadores web cuando leíste "worklet", no estás equivocado. Tienen mucha superposición conceptual. ¿Por qué hay algo nuevo si ya tenemos trabajadores?

El objetivo de Houdini es exponer nuevas APIs para permitir que los desarrolladores web conecten su propio código en el motor de CSS y en los sistemas que los rodean. Es probable que no sea poco realista suponer que algunos de estos fragmentos de código deberán ejecutarse every. single. frame. Algunos lo tienen por definición. Cita de la especificación de Web Worker:

Esto 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 según el tipo de worklet. Son ligeros y de corta duración.

API de pintura de CSS (spec)

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

Worklet del compositor

La API descrita 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 trasladó a WICG y se iterará, es la que más me entusiasma. El motor CSS subcontrata algunas operaciones a la tarjeta gráfica de tu computadora, aunque esto depende de la tarjeta gráfica y del dispositivo en general.

Un navegador generalmente toma el árbol del DOM y, según criterios específicos, decide otorgar su propia capa a algunas ramas y subárboles. Estos subárboles se pintan en él (quizás usar un worklet de pintura en el futuro). Por último, todas estas capas individuales, ahora pintadas, se apilan y se posicionan una encima de la otra, respetando los índices z, las transformaciones 3D y demás, para obtener la imagen final que se ve 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 se vuelvan a pintar cuando la página se desplace un poco. En su lugar, puedes volver a usar las capas del marco anterior y volver a ejecutar el compositor con la posición de desplazamiento actualizada. Eso hace que todo sea más rápido. Esto nos ayuda a alcanzar los 60 fps.

Worklet del compositor

Como su nombre lo indica, el worklet del compositor te permite conectarte al compositor e influir en la posición de la capa de un elemento, que ya se pintó, y su colocación sobre las demás capas.

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

Esta es una implementación completa del desplazamiento con paralaje, mediante el trabajo 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 puedas probarlo, obviamente, con un impacto en el rendimiento mucho mayor.

Worklet de diseño (spec)

Se propuso el primer borrador de especificaciones real. Queda poco para la implementación.

Nuevamente, la especificación para esto está prácticamente vacía, pero el concepto es interesante: escribe tu propio diseño. Se supone que el worklet de diseño te permite realizar 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 cortes puedan generar una mejora en el rendimiento. Imagina un sitio web compuesto solo de mosaicos, como Windows 10 o un diseño de mampostería. No se usa el posicionamiento absoluto y fijo, tampoco se usa z-index ni los elementos se superponen o tienen algún tipo de borde o desbordamiento. Poder omitir todas estas verificaciones durante el rediseño podría aumentar 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 (modelo de objetos CSS o modelo de objetos en cascada) aborda un problema que probablemente todos nos hemos encontrado y que aprendimos a soportar. Ilustrar con una línea de JavaScript:

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

Estamos haciendo cálculos matemáticos: convertimos un número en una cadena para agregar una unidad solo para que el navegador la analice y la vuelva a convertir en un número para el motor CSS. Esto se complica aún más cuando manipulas transformaciones con JavaScript. No más. A punto de empezar a escribir CSS,

Este borrador es uno de los más maduros y ya se está trabajando en un polyfill. (Renuncia de responsabilidad: Es obvio que usar polyfill agregará aún más sobrecarga de procesamiento). El objetivo 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 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. Si se configura height: calc(5px + 5%), se generaría un 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 se podía verificar 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 su alias no oficial “Variables de CSS”)? Estos son todos, pero con tipos. Hasta ahora, las variables solo podían tener valores de cadena y usaban un enfoque simple de búsqueda y reemplazo. Este borrador no solo te permitiría especificar un tipo para tus variables, sino también definir un valor predeterminado y también influir en el comportamiento de herencia mediante una API de JavaScript. Técnicamente, esto también permitiría animar las propiedades personalizadas con transiciones y animaciones estándar de CSS, lo que también se tiene en cuenta.

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

Métricas de fuentes

Las métricas de fuentes son exactamente lo que parece. ¿Cuál es el cuadro de límite (o los cuadros delimitadores) cuando renderiza la cadena X con la fuente Y con el tamaño Z? ¿Qué sucede si uso anotaciones rubí? Se ha pedido mucho, y Houdini debería hacer realidad sus deseos.

Espera. Hay más calcomanías.

Hay incluso más especificaciones en la lista de borradores de Houdini, pero el futuro de esas versiones es bastante incierto y no son mucho más que marcadores de posición para ideas. Entre los ejemplos, se incluyen comportamientos de desbordamiento personalizados, la API de extensión de sintaxis CSS, la extensión del comportamiento nativo de desplazamiento y otros elementos igualmente ambiciosos, que permiten realizar acciones en la plataforma web que antes no eran posibles.

Demostraciones

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