Slide 1

Slide 1 text

Leveling up creativity on the Web with CSS Houdini Arnelle Balane Software Developer, Newlogic @arnellebalane

Slide 2

Slide 2 text

Arnelle Balane Software Developer at Newlogic Google Developers Expert for Web Technologies I write about Web stuff on my blog, arnellebalane.com @arnellebalane

Slide 3

Slide 3 text

Houdini

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

CSS Houdini

Slide 6

Slide 6 text

allows developers to hook into the browsers’ rendering engines and extend CSS itself CSS Houdini

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

How browsers render a Web page

Slide 10

Slide 10 text

Credits: Una Kravets, DevDoodles

Slide 11

Slide 11 text

How we adopt new Web features

Slide 12

Slide 12 text

Total Time = Days Propose Feature Write Polyfill / Transpiler Plugin Use Feature

Slide 13

Slide 13 text

Propose Feature Write Specification Browser Vendors Implement Wait for Browser Adoption Use Feature Total Time = YEARS!

Slide 14

Slide 14 text

Polyfilling CSS is difficult

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

CSS Houdini

Slide 19

Slide 19 text

CSS Houdini Properties and Values Animation Worklet Typed OM Worklets Paint API Layout API

Slide 20

Slide 20 text

Properties and Values API register custom properties, define their syntax, default values, and inherit behaviour

Slide 21

Slide 21 text

CSS variables?

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

CSS.registerProperty({ name: '--box-color', syntax: '', initialValue: 'red', inherits: false }); Registering a custom property

Slide 27

Slide 27 text

Registering a custom property

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Animating CSS gradients

Slide 32

Slide 32 text

Animating CSS gradients CSS.registerProperty({ name: '--gradient-start', syntax: '', initialValue: 'red', inherits: false });

Slide 33

Slide 33 text

Do I need to register all my CSS variables now?

Slide 34

Slide 34 text

Typed OM expose CSS values as typed JavaScript objects

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Reading CSS values div { /* ... */ width: calc(100% - 3em); transform: translate(10px, 20vh) rotate(1turn) scale(1.25); }

Slide 41

Slide 41 text

Reading CSS values box.style.opacity = 0.25; // number! box.style.opacity; // '0.25'

Slide 42

Slide 42 text

… with Typed OM getComputedStyle(box).width;

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

transform: translate(10px, 20px) scale(1.5);

Slide 46

Slide 46 text

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"} } }

Slide 47

Slide 47 text

Typed OM and inline styles box.style.fontSize = '12px'; box.style.fontSize;

Slide 48

Slide 48 text

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');

Slide 49

Slide 49 text

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"}

Slide 50

Slide 50 text

bit.ly/typed-om

Slide 51

Slide 51 text

Worklets extend low-level parts of the browser rendering pipeline

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

- 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

Slide 56

Slide 56 text

Paint API programmatically generate an image where CSS expects a file

Slide 57

Slide 57 text

Credits: Una Kravets, DevDoodles

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

div { background-image: url("dots.png"); }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

class DotGridWorklet { paint(context, size, props) { } } div { background-image: paint(dot-grid); }

Slide 65

Slide 65 text

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); }

Slide 66

Slide 66 text

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); }

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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; }

Slide 71

Slide 71 text

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: '', initialValue: '6px' }); main.js

Slide 72

Slide 72 text

github.com/arnellebalane/houdini-paint-dot-grid

Slide 73

Slide 73 text

extra-css.netlify.com

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Layout API define custom display values for laying out elements

Slide 76

Slide 76 text

Credits: Una Kravets, DevDoodles

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

div { display: layout(masonry); }

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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}; } }

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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; } } }

Slide 87

Slide 87 text

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}; } }

Slide 88

Slide 88 text

houdini.glitch.me/layout

Slide 89

Slide 89 text

Animation API control animations based on user input in a performant manner

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

main.js await CSS.animationWorklet .addModule('./parallax-worklet.js');

Slide 93

Slide 93 text

main.js await CSS.animationWorklet .addModule('./parallax-worklet.js'); const timeRange = 1000; const scrollTimeline = new ScrollTimeline({ scrollSource: document.scrollingElement, timeRange });

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

houdini.glitch.me/animation

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

ishoudinireadyyet.com

Slide 102

Slide 102 text

ishoudinireadyyet.com

Slide 103

Slide 103 text

css-houdini.rocks/corners-gradient

Slide 104

Slide 104 text

css-houdini.rocks/slanted-backgrounds

Slide 105

Slide 105 text

css-houdini.rocks/rough-boxes

Slide 106

Slide 106 text

Houdini Spellbook houdini.glitch.me/ Is Houdini Ready Yet? ishoudinireadyyet.com CSS Houdini Experiments css-houdini.rocks Resources

Slide 107

Slide 107 text

Thank you! Arnelle Balane Software Developer, Newlogic @arnellebalane