The magic tricks of Houdini A wagic wand for the future of CSS

Logo of CSS Day Faenza, 2019-03-15
My face

Massimo Artizzu

Web dev & architect
at Antreem

Twitter logo / GitHub logo @MaxArt2501

You can find these slides at

QR Code for the presentation's link maxart2501.github.io/css-houdini-talk/cssday-2019/

Let's talk about…

JavaScript Vincent Vega confused

Wait!
It gets better!

Let's talk about JavaScript

on Internet Explorer 6

Even Microsoft had good ideas!

David Silverman's meme face Usually, with a very poor execution…

Internet Explorer

introduced 2 ways JavaScript could extend CSS:

IE's CSS expressions

.indicator {
  width: 100%;
  color: expression(this.clientWidth > 400 ? 'red' : 'blue');
}

IE's custom behaviors

<!-- boxsizing.htc -->
<component lightWeight="true">
<attach event="onpropertychange" onevent="checkPropertyChange()" />
<attach event="ondetach" onevent="restore()" />
<script type="text/javascript">//<![CDATA[
function checkPropertyChange(){ ... }
...
//]]></script>
</component>

👍

👎

Hank Scorpio flamethrowing

KILL IT WITH FIRE

What now?

CSS is evolving:

But no matter what the W3C does

CSS will never cover everyone's use cases.

In short…

We're still like this:

Peter Griffin meddling with blinds

So, what can we do?

🤔

Enter Houdini

A project inside the W3C to expose parts of the CSS engine to web developers

Houdini modules

Style Layout Paint Composite Rendering cycle
Collaboration effort 🤝

Worklets

Think of Workers, but lightweight:

Are you ready?

Paint API

What's that for?

Allows to draw an image, to be used as a background, a border, a mask…
.delaunay {
  background-color: #f60;
  background-image: paint(delaunay);
}
// delaunay.js
class DelaunayModule {
  paint(context, geometry, properties) { ... }

  static get inputProperties() {
    return [ '--delaunay-point-area', ... ];
  }
}

registerPaint('delaunay', DelaunayModule);

// main.js
CSS.paintWorklet.addModule('./delaunay.js').then(...);
Of course the module must adhere to the same-origin policy, must be on https or localhost, yadda yadda…

paint(context, geometry, properties)

context is a stripped-down CanvasRenderingContext2D

👉 geometry has just two numeric properties:

↔ width and ↕ height

👉 properties is a Map of the CSS properties given by inputProperties

Where can I use it?

Some of these behave weirdly, but kinda work. Take note there's must be something to paint.

Typed OM

element.style.fontSize = '20px';
becomes
element.attributeStyleMap.set('font-size', CSS.px(20));
Guy looking confused How is that an improvement?!
One word:

Performance

and other stuff

(ok, that's 4)

What usually happens

const width = someComputation();
element.style.width = `${width}px`;

number ➡ string ➡ CSS parse ➡ CSS value

What happens with Typed OM

const width = someComputation();
element.attributeStyleMap.set('width', CSS.px(width));

number ➡ string ➡ CSS parse ➡ CSS value

Style maps

What's .attributeStyleMap?

It's a Map for style properties

StylePropertyMap

It means you can set numbers, strings or typed values; and you get only typed values.

There are two defined on elements:

And by the way

paint(ctx, geom, properties)

That's a StylePropertyMap too.

Other perks

Properties and values

#1: the cascade

.box {
  width: 20em;
  width: red;
}
width: red gets ignored

What happens now?

.box {
  --box-width: 20em;
  --box-width: red;
  width: var(--box-width);
}
It's --box-width: 20em that gets ignored!

#2: animations

.box { animation: streeetch 2s infinite; }
@keyframes streeetch {
  from { width: 10em; }
  to   { width: 20em; }
}
BOX
.box {
  width: var(--box-width);
  animation: streeetch 2s infinite;
}
@keyframes streeetch {
  from { --box-width: 10em; }
  to   { --box-width: 20em; }
}
BOX

Why?
🤔

width is known to be a length, while --box-width is not

Set the type of a custom property

CSS.registerProperty({
  name: '--box-width',
  syntax: '<length>',
  initialValue: CSS.px(0),
  inherits: false
});

Next step 🔭

Register properties in CSS 🎉

@property --box-width {
  syntax: '<length>';
  initial-value: 0px;
  inherits: false;
}

What we've seen so far

🎨 Paint API

Draw images fast in a worklet

⚡ Typed OM

Fast and less error-prone values for CSS

🔢 Properties and Values

Making custom properties first class citizens

Hello! 👋
registerPaint('ripple', class {
  static get inputProperties() {
    return [ '--ripple-color', ... ];
  }
  paint(ctx, { width, height }, props) {
    const color = props.get('--ripple-color');
    ...
    ctx.fillStyle = color;
    ctx.arc(centerX.value, centerY.value,
      radius.value * farthest / 100, 0, Math.PI * 2);
    ctx.fill();
  }
});
CSS.registerProperty({
  name: '--ripple-color',
  syntax: '<color>',
  initialValue: '#fff0',
  inherits: false
});
CSS.registerProperty({
  name: '--ripple-radius',
  syntax: '<percentage>',
  ...
});
...
const duration =
  button.computedStyleMap()
    .get('--ripple-duration')
    .to('ms').value;

button.animate([{
  '--ripple-color': '#ffffff80',
  '--ripple-radius': CSS.percent(0)
}, {
  '--ripple-color': '#ffffff00',
  '--ripple-radius': CSS.percent(100)
}], duration);

We can already do a lot, but…

Can I use it in production?

Is Houdini Ready Yet screenshot There's a handy table reporting the status of Houdini's specs at W3C, and their implementation among the major browsers Is Houdini ready yet.com Serously, that's the site. Maintained by Surma, the guy who recently talked about Houdini at the last Chrome Dev Summit

Layout API

A house crumbling because of termites
Mock-solid 😰
display: layout(my-layout)
😉
😎
😋
🤣
CSS.layoutWorklet.addModule('./my-layout.js')
registerLayout('my-layout', class {
  async layout(children, edges, constraints, properties, breakToken) {
    😱
  }
  async intrinsicSizes(children, edges, properties) { 🤔 }
  static inputProperties = ['--foo'];
  static childrenInputProperties = ['--bar'];
  static layoutOptions = {childDisplay: 'normal', sizing: 'block-like'};
});
I won't explain all this, it could be overwhelming. Doing a layout is a very complex task. It would also be futile, as this spec is bound to change.

What can we do?

Easy masonry 🏠

Example of masonry layout googlechromelabs.github.io
/houdini-samples

Animation Worklet

CSS Animations
requestAnimationFrame
Not really but eh, there's 'animation' in the name
Web Animation API

Web Animation API

Can do a lot…

Could do much more!

registerAnimator('my-animator', class {
  constructor(options = {}) {
    this.options = options;
  }
  animate(currentTime, effect) {
    const newTime = bendTheTime(currentTime);
    effect.localTime = newTime;
  }
});
await CSS.animationWorklet.addModule('./my-anim.js');

const effect = new KeyframeEffect(el, frames);
const animation = new WorkletAnimation(
  'my-animator',
  effect
);

animation.play();

Timelines 💫

new Animation(
  effect,
  animationOptions,
  document.timeline
);

What is "time"? 🤔

Extend the concept to a cursor in an interval

ScrollTimeline 🎉

const timeline = new ScrollTimeline({
  scrollSource: document.scrollElement,
  orientation: 'vertical',
  timeRange: 1000
});
We must use it in a worklet animation, though

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed ullamcorper morbi tincidunt ornare massa eget. Orci porta non pulvinar neque laoreet suspendisse interdum consectetur. Sed adipiscing diam donec adipiscing tristique risus nec. Cras pulvinar mattis nunc sed blandit libero volutpat sed. Porta non pulvinar neque laoreet. Pulvinar neque laoreet suspendisse interdum consectetur libero id faucibus nisl. Adipiscing at in tellus integer feugiat scelerisque. Orci a scelerisque purus semper. Proin fermentum leo vel orci porta non. In massa tempor nec feugiat nisl pretium fusce id. Tortor posuere ac ut consequat semper viverra. Ac orci phasellus egestas tellus rutrum tellus pellentesque eu tincidunt. Facilisi etiam dignissim diam quis enim lobortis scelerisque fermentum.

Nullam ac tortor vitae purus. A lacus vestibulum sed arcu non odio. Lectus vestibulum mattis ullamcorper velit sed. Tristique sollicitudin nibh sit amet commodo nulla facilisi. Pulvinar mattis nunc sed blandit libero. Nunc pulvinar sapien et ligula ullamcorper malesuada proin libero nunc. Facilisis mauris sit amet massa vitae tortor. Blandit turpis cursus in hac habitasse platea dictumst quisque sagittis. Vel pharetra vel turpis nunc eget lorem dolor sed. Amet justo donec enim diam vulputate ut pharetra sit amet. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus. Sed elementum tempus egestas sed. In tellus integer feugiat scelerisque. Elit sed vulputate mi sit amet mauris commodo quis.

Auctor neque vitae tempus quam. Turpis tincidunt id aliquet risus feugiat in ante metus. Libero nunc consequat interdum varius sit amet mattis. Blandit cursus risus at ultrices. Morbi tristique senectus et netus et. Nulla at volutpat diam ut. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Tempus egestas sed sed risus pretium quam. Feugiat nibh sed pulvinar proin gravida hendrerit. Ut enim blandit volutpat maecenas volutpat blandit aliquam. Odio eu feugiat pretium nibh ipsum consequat nisl vel. Elit ut aliquam purus sit amet. Dignissim diam quis enim lobortis scelerisque fermentum. Porttitor leo a diam sollicitudin. Amet massa vitae tortor condimentum lacinia. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Metus aliquam eleifend mi in nulla posuere sollicitudin. Dolor magna eget est lorem ipsum dolor sit amet. Vulputate ut pharetra sit amet aliquam id diam maecenas.

Et molestie ac feugiat sed lectus vestibulum. Ut lectus arcu bibendum at varius vel pharetra vel. Vitae tortor condimentum lacinia quis vel eros donec. Elit sed vulputate mi sit. Vel pharetra vel turpis nunc eget lorem dolor. Massa vitae tortor condimentum lacinia quis vel eros donec. Purus in massa tempor nec. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat. Volutpat blandit aliquam etiam erat velit scelerisque in. Enim diam vulputate ut pharetra sit amet aliquam id. Convallis a cras semper auctor neque vitae tempus quam. Pellentesque sit amet porttitor eget dolor. Sollicitudin ac orci phasellus egestas. Ultricies integer quis auctor elit sed vulputate mi sit. Leo in vitae turpis massa sed elementum tempus. Nisi lacus sed viverra tellus. Eget nulla facilisi etiam dignissim diam quis enim lobortis. In nisl nisi scelerisque eu ultrices vitae auctor eu augue. Fames ac turpis egestas integer eget. A pellentesque sit amet porttitor eget.

Dignissim enim sit amet venenatis urna. In nibh mauris cursus mattis molestie a iaculis at. Vitae tortor condimentum lacinia quis vel eros donec ac. Leo duis ut diam quam nulla porttitor massa. Semper eget duis at tellus at urna condimentum mattis pellentesque. Non consectetur a erat nam at lectus urna. Fermentum leo vel orci porta non pulvinar neque. Ornare arcu odio ut sem nulla pharetra. Sit amet nisl suscipit adipiscing bibendum est ultricies integer. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. At tellus at urna condimentum mattis pellentesque id nibh. Nec sagittis aliquam malesuada bibendum arcu vitae. Arcu non sodales neque sodales ut etiam sit amet nisl. Vulputate odio ut enim blandit volutpat maecenas. Magna ac placerat vestibulum lectus mauris ultrices eros. Varius quam quisque id diam vel quam elementum pulvinar. Arcu dui vivamus arcu felis bibendum ut tristique. Pharetra diam sit amet nisl suscipit. Blandit aliquam etiam erat velit. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis a.

Other timelines? 🤞

InputTimeline Not here yet…
Old map of Northern Africa
Hic Sunt Leones

Font metrics API

Layout + font = ❤

An explainer of font metrics

Answering questions

¯\_(ツ)_/¯

Parser API

Allowing to parse

A literal strawman
¯\_(ツ)_/¯

Links #1

Links #2

for (const question of questions) {
  await answer(question)
}