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.

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. 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 });
  4. 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
  5. Reading CSS values const width = getComputedStyle(box).width; // '200px' parseInt(width.replace('px',

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

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

    - 3em); transform: translate(10px, 20vh) rotate(1turn) scale(1.25); }
  8. 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"} } }
  9. 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"}
  10. - 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
  11. main.js class DotGridWorklet { paint(context, size, props) { } }

    registerPaint('dot-grid', DotGridWorklet); dot-grid-worklet.js CSS.paintWorklet .addModule('./dot-grid-worklet.js');
  12. class DotGridWorklet { paint(context, size, props) { } } div

    { background-image: paint(dot-grid); }
  13. 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); }
  14. 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); }
  15. class DotGridWorklet { paint(context, size, props) { const dotSize =

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

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

    paint(context, size, props) { const dotSize = 6; // ... } } div { background-image: paint(dot-grid); --dot-size: 6px; }
  18. 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; }
  19. 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
  20. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { } }

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

    } registerLayout('masonry', MasonryWorklet); masonry-worklet.js CSS.layoutWorklet .addModule('masonry-worklet.js');
  22. 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}; } }
  23. class MasonryWorklet { *layout(children, edges, constraints, styleMap) { const childFragments

    = yield children.map((child) => { return child.layoutNextFragment({fixedInlineSize: 200}); }); } }
  24. 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; } } }
  25. 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}; } }
  26. class ParallaxAnimator { constructor(options) { this.rate = options.rate; } animate(currentTime,

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

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

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

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

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

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

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

    'translateY(-200vh)'} ], {duration: timeRange} ), scrollTimeline, {rate: 4} ).play();