Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Leveling up creativity on the Web with CSS Houdini

Leveling up creativity on the Web with CSS Houdini

Have you ever discovered a new CSS feature, found it to be really cool, and then realized that it will still take a long time before you can use it?

How about having to deal with CSS cross-browser incompatibilities and inconsistent behavior? Wouldn't it be great to have a way to polyfill a CSS feature and start using them today?

CSS Houdini is a new set of APIs that, for the first time, allows us developers to extend CSS itself and hook into the browsers' rendering engines. This gives us the power to fix the problems with CSS once and for all. And also be creative and have fun along the way! In this talk, we will explore what CSS Houdini is, how to use it to fix CSS problems, and how to build nice things with it.

32c638e7a3a466d182705fb4370cbb2e?s=128

Arnelle Balane

November 16, 2019
Tweet

More Decks by Arnelle Balane

Other Decks in Technology

Transcript

  1. Leveling up creativity on the Web with CSS Houdini Arnelle

    Balane Software Developer, Newlogic @arnellebalane
  2. Arnelle Balane Software Developer at Newlogic Google Developers Expert for

    Web Technologies I write about Web stuff on my blog, arnellebalane.com @arnellebalane
  3. Houdini

  4. None
  5. CSS Houdini

  6. allows developers to hook into the browsers’ rendering engines and

    extend CSS itself CSS Houdini
  7. None
  8. None
  9. How browsers render a Web page

  10. Credits: Una Kravets, DevDoodles

  11. How we adopt new Web features

  12. Total Time = Days Propose Feature Write Polyfill / Transpiler

    Plugin Use Feature
  13. Propose Feature Write Specification Browser Vendors Implement Wait for Browser

    Adoption Use Feature Total Time = YEARS!
  14. Polyfilling CSS is difficult

  15. None
  16. None
  17. None
  18. CSS Houdini

  19. CSS Houdini Properties and Values Animation Worklet Typed OM Worklets

    Paint API Layout API
  20. Properties and Values API register custom properties, define their syntax,

    default values, and inherit behaviour
  21. CSS variables?

  22. div { /* ... */ --box-color: red; background-color: var(--box-color); }

  23. div { /* ... */ /* --box-color: red; */ background-color:

    var(--box-color); }
  24. div { /* ... */ /* --box-color: red; */ background-color:

    var(--box-color, red); }
  25. div { /* ... */ --box-color: 12px; background-color: var(--box-color, red);

    }
  26. CSS.registerProperty({ name: '--box-color', syntax: '<color>', initialValue: 'red', inherits: false });

    Registering a custom property
  27. Registering a custom property <length> <angle> <number> <time> <percentage> <url>

    <color> <custom-ident> <image> etc. <length> | <percentage> <length>+ <length># CSS.registerProperty({ name: '--box-color', syntax: '<color>', initialValue: 'red', inherits: false });
  28. div { /* ... */ /* --box-color: red; */ background-color:

    var(--box-color); }
  29. div { /* ... */ --box-color: 12px; background-color: var(--box-color); }

  30. div { /* ... */ --gradient-start: red; background-image: linear-gradient(var(--gradient-start), blue);

    transition: --gradient-start 1s ease; } div:hover { --gradient-start: yellow; } Animating CSS gradients
  31. Animating CSS gradients

  32. Animating CSS gradients CSS.registerProperty({ name: '--gradient-start', syntax: '<color>', initialValue: 'red',

    inherits: false });
  33. Do I need to register all my CSS variables now?

  34. Typed OM expose CSS values as typed JavaScript objects

  35. div { /* ... */ width: 200px; height: 200px; }

  36. const box = document.querySelector('div'); const width = getComputedStyle(box).width; Reading CSS

    values
  37. Reading CSS values const width = getComputedStyle(box).width; // '200px'

  38. Reading CSS values const width = getComputedStyle(box).width; // '200px' parseInt(width.replace('px',

    '')); // 200 parseInt(width.replace(/\D+$/, '')); // 200 parseInt(width); // 200 // ...and more
  39. Reading CSS values const width = getComputedStyle(box).width; // '200px' parseInt(width.replace('px',

    '')); // 200 parseInt(width.replace(/\D+$/, '')); // 200 parseInt(width); // 200 // ...and more
  40. Reading CSS values div { /* ... */ width: calc(100%

    - 3em); transform: translate(10px, 20vh) rotate(1turn) scale(1.25); }
  41. Reading CSS values box.style.opacity = 0.25; // number! box.style.opacity; //

    '0.25'
  42. … with Typed OM getComputedStyle(box).width;

  43. getComputedStyle(box).width; box.computedStyleMap().get('width'); … with Typed OM

  44. getComputedStyle(box).width; box.computedStyleMap().get('width'); » CSSUnitValue {value: 200, unit: "px"} … with

    Typed OM
  45. transform: translate(10px, 20px) scale(1.5);

  46. transform: translate(10px, 20px) scale(1.5); » CSSTransformValue { 0: CSSTranslate {

    x: CSSUnitValue {value: 10, unit: "px"} y: CSSUnitValue {value: 20, unit: "px"} z: CSSUnitValue {value: 0, unit: "px"} } 1: CSSScale { x: CSSUnitValue {value: 1.5, unit: "number"} y: CSSUnitValue {value: 1.5, unit: "number"} z: CSSUnitValue {value: 1, unit: "number"} } }
  47. Typed OM and inline styles box.style.fontSize = '12px'; box.style.fontSize;

  48. Typed OM and inline styles box.style.fontSize = '12px'; box.style.fontSize; box.attributeStyleMap.set('font-size',

    CSS.px(12)); box.attributeStyleMap.get('font-size');
  49. Typed OM and inline styles box.style.fontSize = '12px'; box.style.fontSize; box.attributeStyleMap.set('font-size',

    CSS.px(12)); box.attributeStyleMap.get('font-size'); » CSSUnitValue {value: 12, unit: "px"}
  50. bit.ly/typed-om

  51. Worklets extend low-level parts of the browser rendering pipeline

  52. class MyWorklet { // ... } my-worklet.js

  53. class MyWorklet { // ... } register('worklet-name', MyWorklet); my-worklet.js

  54. class MyWorklet { // ... } register('worklet-name', MyWorklet); worklet.addModule('./my-worklet.js'); my-worklet.js

    main.js
  55. - Run in a separate process, not the main thread

    - No access to global items like setTimeout, etc. - Browsers determine when they are run, not us! Worklets
  56. Paint API programmatically generate an image where CSS expects a

    file
  57. Credits: Una Kravets, DevDoodles

  58. None
  59. class DotGridWorklet { paint(context, size, props) { } } dot-grid-worklet.js

  60. class DotGridWorklet { paint(context, size, props) { } } registerPaint('dot-grid',

    DotGridWorklet); dot-grid-worklet.js
  61. main.js class DotGridWorklet { paint(context, size, props) { } }

    registerPaint('dot-grid', DotGridWorklet); dot-grid-worklet.js CSS.paintWorklet .addModule('./dot-grid-worklet.js');
  62. div { background-image: url("dots.png"); }

  63. div { background-image: paint(dot-grid); }

  64. class DotGridWorklet { paint(context, size, props) { } } div

    { background-image: paint(dot-grid); }
  65. class DotGridWorklet { paint(context, size, props) { const dotSize =

    6; const dotSpacing = 6; const dotColor = 'red'; // ... context.beginPath(); context.arc(...); context.fill(); } } div { background-image: paint(dot-grid); }
  66. class DotGridWorklet { paint(context, size, props) { const dotSize =

    6; const dotSpacing = 6; const dotColor = 'red'; // ... context.beginPath(); context.arc(...); context.fill(); } } div { background-image: paint(dot-grid); }
  67. class DotGridWorklet { paint(context, size, props) { const dotSize =

    6; // ... } } div { background-image: paint(dot-grid); }
  68. class DotGridWorklet { paint(context, size, props) { const dotSize =

    6; // ... } } div { background-image: paint(dot-grid); --dot-size: 6px; }
  69. class DotGridWorklet { static get inputProperties() { return ['--dot-size']; }

    paint(context, size, props) { const dotSize = 6; // ... } } div { background-image: paint(dot-grid); --dot-size: 6px; }
  70. class DotGridWorklet { static get inputProperties() { return ['--dot-size']; }

    paint(context, size, props) { const dotSize = props.get('--dot-size').value; // ... } } div { background-image: paint(dot-grid); --dot-size: 6px; }
  71. class DotGridWorklet { static get inputProperties() { return ['--dot-size']; }

    paint(context, size, props) { const dotSize = props.get('--dot-size').value; // ... } } div { background-image: paint(dot-grid); --dot-size: 6px; } CSS.registerProperty({ name: '--dot-size', syntax: '<length>', initialValue: '6px' }); main.js
  72. github.com/arnellebalane/houdini-paint-dot-grid

  73. extra-css.netlify.com

  74. None
  75. Layout API define custom display values for laying out elements

  76. Credits: Una Kravets, DevDoodles

  77. None
  78. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { } }

    masonry-worklet.js
  79. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { } }

    registerLayout('masonry', MasonryWorklet); masonry-worklet.js
  80. main.js class MasonryWorklet { *layout(children, edges, constraints, styleMap) { }

    } registerLayout('masonry', MasonryWorklet); masonry-worklet.js CSS.layoutWorklet .addModule('masonry-worklet.js');
  81. div { display: layout(masonry); }

  82. div { display: layout(masonry); --masonry-columns: 3; --masonry-gap: 20px; }

  83. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { } }

  84. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { const inlineSize

    = constraints.fixedInlineSize; const padding = parseInt(styleMap.get('--padding')); const columnValue = styleMap.get('--columns'); let columns = parseInt(columnValue); if (columnValue == 'auto' || !columns) { columns = Math.ceil(inlineSize / 350); } const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns; const childFragments = yield children.map((child) => { return child.layoutNextFragment({fixedInlineSize: childInlineSize}); }); let autoBlockSize = 0; const columnOffsets = Array(columns).fill(0); for (let childFragment of childFragments) { const min = columnOffsets.reduce((acc, val, idx) => { if (!acc || val < acc.val) { return {idx, val}; } return acc; }, {val: +Infinity, idx: -1}); childFragment.inlineOffset = padding + (childInlineSize + padding) * min.idx; childFragment.blockOffset = padding + min.val; columnOffsets[min.idx] = childFragment.blockOffset + childFragment.blockSize; autoBlockSize = Math.max(autoBlockSize, columnOffsets[min.idx] + padding); } return {autoBlockSize, childFragments}; } }
  85. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { const childFragments

    = yield children.map((child) => { return child.layoutNextFragment({fixedInlineSize: 200}); }); } }
  86. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { const childFragments

    = yield children.map((child) => { return child.layoutNextFragment({fixedInlineSize: 200}); }); for (let childFragment of childFragments) { childFragment.inlineOffset = 100; childFragment.blockOffset = 100; } } }
  87. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { const childFragments

    = yield children.map((child) => { return child.layoutNextFragment({fixedInlineSize: 200}); }); for (let childFragment of childFragments) { childFragment.inlineOffset = 100; childFragment.blockOffset = 100; } return {autoBlockSize: 500, childFragments}; } }
  88. houdini.glitch.me/layout

  89. Animation API control animations based on user input in a

    performant manner
  90. class ParallaxAnimator { constructor(options) { this.rate = options.rate; } animate(currentTime,

    effect) { effect.localTime = currentTime * this.rate; } } parallax-worklet.js
  91. class ParallaxAnimator { constructor(options) { this.rate = options.rate; } animate(currentTime,

    effect) { effect.localTime = currentTime * this.rate; } } registerAnimator('parallax', ParallaxAnimator); parallax-worklet.js
  92. main.js await CSS.animationWorklet .addModule('./parallax-worklet.js');

  93. main.js await CSS.animationWorklet .addModule('./parallax-worklet.js'); const timeRange = 1000; const scrollTimeline

    = new ScrollTimeline({ scrollSource: document.scrollingElement, timeRange });
  94. new WorkletAnimation( 'parallax', new KeyframeEffect( document.querySelector('#one'), [ {transform: 'translateY(0)'}, {transform:

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();
  95. new WorkletAnimation( 'parallax', new KeyframeEffect( document.querySelector('#one'), [ {transform: 'translateY(0)'}, {transform:

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();
  96. new WorkletAnimation( 'parallax', new KeyframeEffect( document.querySelector('#one'), [ {transform: 'translateY(0)'}, {transform:

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();
  97. new WorkletAnimation( 'parallax', new KeyframeEffect( document.querySelector('#one'), [ {transform: 'translateY(0)'}, {transform:

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();
  98. new WorkletAnimation( 'parallax', new KeyframeEffect( document.querySelector('#one'), [ {transform: 'translateY(0)'}, {transform:

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();
  99. houdini.glitch.me/animation

  100. None
  101. ishoudinireadyyet.com

  102. ishoudinireadyyet.com

  103. css-houdini.rocks/corners-gradient

  104. css-houdini.rocks/slanted-backgrounds

  105. css-houdini.rocks/rough-boxes

  106. Houdini Spellbook houdini.glitch.me/ Is Houdini Ready Yet? ishoudinireadyyet.com CSS Houdini

    Experiments css-houdini.rocks Resources
  107. Thank you! Arnelle Balane Software Developer, Newlogic @arnellebalane