CSS Houdini

Tibor Pilz

23.08.2023

CSS Houdini is:

  • A collection of low-level APIs which expose parts of the CSS engine
  • Not production-ready - and might never be

State of CSS Houdini

The CSS Typed Object Model

  • CSS has an object model (CSSOM)
  • Access is done via the style attribute
const el = document.getElementById('test-element');
el.style.opacity = 0.3;
el.style.fontSize = '24px';

Problems with CSSOM

console.log(typeof el.style.opacity); // 'string'
el.style.opacity += 0.1;
console.log(el.style.opacity); // '0.30.1' - wat
  • These attributes are always strings.

Problems with CSSOM

const { fontSize, opacity } = footer.style;
const parsedFontSize = parseFloat(fontSize);

el.style.fontSize = `${parsedFontSize + 5}px`;

const parsedOpacity = parseFloat(opacity);
el.style.opacity = parsedOpacity + 0.1;
  • Working with unit values is laborious, as it requires constant parsing or serialization of strings.

CSS Typed OM - attributeStyleMap

el.attributeStyleMap.get('opacity').value; // 0.5
el.attributeStyleMap.get('fontSize').value; // 29
el.attributeStyleMap.get('fontSize').unit; // 'px'
  • el.attributeStyleMap.get returns a CSSUnitValue with a value and a unit.

Setting CSS Typed OM Values

footer.attributeStyleMap.set('fontSize', CSS.px(29));
footer.attributeStyleMap.set('fontSize', '29px'); // Just as valid

CSS Style Values

  • Numbers are represented in two ways:
    1. CSSUnitValue: corresponds to, e.g., “42px”
    2. CSSMathValue: corresponds to, e.g., “calc(56em + 10%)”

CSS Unit Values

  • To create CSSUnitValues, these factory methods exist (among others):
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'

CSSMathValues

const cssSum = new CSSMathSum(CSS.vw(100), CSS.px(-10));

cssSum.toString(); // "calc(100vw + -10px)"
  • Similar classes exist for other CSS calc expressions, like CSSMathNegate, CSSMathInvert, CSSMathProduct, etc.

CSSStyleValues - Beyond

  • CSSStyleValues support additional features:

CSS calc shorthand

CSS.px(1).add(CSS.px(2)); // { value: 3, unit: 'px' }
CSS.px(1).add(CSS.vw(50)).toString(); // "calc(1px + 50vw)"

Unit Conversion

CSS.px(1).to('mm'); // { value: 0.26444, unit: "mm" }

Equality Comparison

const width = CSS.px(200);
CSS.px(200).equals(width); // true

Property and Values API

  • Basically CSS custom properties…
  • …but with types!
  • enables:
    • typing
    • inheritance
    • initial values
    • animations ✨

Example

  .box {
    --color-top: turquoise;
    --color-bottom: orange;
    
    width: 250px;
    height: 250px;
    
    background: linear-gradient(var(--color-top), var(--color-bottom));
    
    transition: 1s;
    transition-property: --color-top, --color-bottom;
  }

  .box:hover {
    --color-top: orange;
    --color-bottom: turquoise;
  }

Example - First Try

Example - Extension

CSS.registerProperty({
  name: '--color-top',
  syntax: '<color>',
  inherits: false,
  initialValue: '#c0ffee',
});

CSS.registerProperty({
  name: '--color-bottom',
  syntax: '<color>',
  inherits: false,
  initialValue: '#c0ffee',
})

Example - Second Try

CSS Painting API

  • Enables custom implementation of the paint() CSS-function
  • Uses PaintWorklets, which receive a ctx
  • Really similar to working with a canvas

Paint Worklet

A worklet according to the CSS Houdini spec

main.js

CSS.paintWorklet.addModule('my-paint-worklet.js')

my-paint-worklet.js

class MyPainter {
  paint(ctx, geometry, properties) {
    // go Pollock
  }
}

registerPaint('myPainter', MyPainter)

main.css

.box {
  background-image: paint('myPainter')
}

Example

Fin

// reveal.js plugins