Slide 1

Slide 1 text

CSS Houdini- the bridge between CSS, JavaScript and the browser Serg Hospodarets @malyw

Slide 2

Slide 2 text

About me

Slide 3

Slide 3 text

CSS Custom Properties

Slide 4

Slide 4 text

Preprocessors Variables and Operators (+, -, *, /, %) $font-size: 10px; $font-family: Helvetica, sans-serif; body { font: $font-size $font-family; } .mark{ font-size: 1.5 * $font-size; }

Slide 5

Slide 5 text

Preprocessor variable problems Each preprocessor has own syntax Additional setup Recompilation after changes is required Compilation takes time Absence of interaction with the browser Not aware of the DOM structure

Slide 6

Slide 6 text

Native CSS Syntax /* declaration */ --VAR_NAME: ; /* usage */ var(--VAR_NAME) /* root element selector (global scope) */ /* usually */ :root {/* make available for whole the app */ /* CSS variables declarations */ --main-color: #ff00ff; --main-bg: rgb(200, 255, 255); } body { /* variable usage */ color: var(--main-color); } "--" prefix picked to prevent preprocessors to compile Custom Properties

Slide 7

Slide 7 text

Declaration and usage

Slide 8

Slide 8 text

Supported in all the major browsers

Slide 9

Slide 9 text

Valid examples :root{ --main-color: #4d4e53; --main-bg: rgb(255, 255, 255); --logo-border-color: rebeccapurple; --header-height: 68px; --content-padding: 10px 20px; --base-line-height: 1.428571429; --transition-duration: .35s; --external-link: "external link"; --margin-top: calc(2vh + 20px); /* Valid CSS custom properties */ /* can be reused later in, say, JavaScript. */ --foo: if(x > 5) this.width = 10; }

Slide 10

Slide 10 text

Declaration and use cases /* Default values */ .box{ /*--- Default values ---*/ /* 10px is used */ margin: var(--possibly-non-existent-value, 10px); /* The --main-padding variable is used */ /* if --box-padding is not defined. */ padding: var(--box-padding, var(--main-padding)); } /* Reuse values in other vars */ .box__highlight::after{ --box-text: 'This is my box'; /* Equal to --box-highlight-text:'This is my box with highlight'; */ --box-highlight-text: var(--box-text)' with highlight'; content: var(--box-highlight-text); }

Slide 11

Slide 11 text

Declaration and use cases

Slide 12

Slide 12 text

CSS-Wide Keywords and "all" Property .common-values{ /* applies the value of the element’s parent. */ --border: inherit; /* applies the initial value as defined in the CSS specification (an empty value, or nothing in some cases of CSS custom properties). */ --bgcolor: initial; /* applies the inherited value if a property is normally inherited (as in the case of custom properties) or the initial value if the property is normally not inherited. */ --padding: unset; /* resets the property to the default value (user agent’s) (an empty value in the case of CSS custom properties). */ --animation: revert; all: initial; /* reset all other CSS styles */ /* Future? */ --: initial; /* reset all CSS custom properties */ }

Slide 13

Slide 13 text

All Value Parts Can Be Changed Individually /* Separate values in CSS Custom Properties */ .transform { --scale: scale(2); --rotate: rotate(10deg); transform: var(--scale) var(--rotate); } .transform:hover{ --rotate: rotate(90deg); } .transform { transform: scale(2) rotate(10deg); } .transform:hover{ transform: scale(2) rotate(90deg); }

Slide 14

Slide 14 text

Emulating non- existing CSS rule

Slide 15

Slide 15 text

Operations: +, -, *, / :root { --block-font-size: 1rem; } .block__highlight { /* DOESN'T WORK */ font-size: var(--block-font-size)*1.5; } CSS calc() to the rescue (for values) :root { --block-font-size: 1rem; } .block__highlight { /* WORKS */ font-size: calc(var(--block-font-size)*1.5); }

Slide 16

Slide 16 text

All together: calc() and change values separately

Slide 17

Slide 17 text

Calculate all the app colors from the base-color (theming)

Slide 18

Slide 18 text

Changes to Custom Props have immediate effect // SCSS .box { $indent: 30px; margin: $indent; /* 30px */ /* is ignored as changed after value is applied */ $indent: 50px; } .box:hover{ /* is ignored as no assignement is provided after this */ $indent: 80px; /* margin: $indent; to apply */ } // CSS .box { --indent: 30px; margin: var(--indent); /* 50px */ /* is applied as native variables are alive as other CSS props */ --indent: 50px; } .box:hover{ /* is applied, so margin: 80px; on hover */ --indent: 80px; }

Slide 19

Slide 19 text

CSS Properties are aware ​ of the DOM structure // SCSS .text { $text-size: 20px; font-size: $text-size; } .active { $text-size: 30px; } /* CSS */ .text { --text-size: 20px; font-size: var(--text-size); } .active { --text-size: 30px; }
.text
.text.active

Slide 20

Slide 20 text

Using Custom Properties With JavaScript const breakpointsData = document.querySelector('.breakpoints-data'); // GET const phone = getComputedStyle(breakpointsData) .getPropertyValue('--phone'); // SET breakpointsData.style .setProperty('--phone', 'custom'); .breakpoints-data { --phone: 480px; --tablet: 800px; } pass breakpoints data from CSS read values... assign CSS value calculated in JS update UI depending on the application state...

Slide 21

Slide 21 text

Rotate page elements using CSS variables

Slide 22

Slide 22 text

Check if supported /* CSS */ @supports ( (--a: 0)) { /* supported */ } @supports ( not (--a: 0)) { /* not supported */ } // JavaScript const isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--a', 0); if (isSupported) { /* supported */ } else { /* not supported */ } if(isSupported){ removeCss('without-css-custom-properties.css'); loadCss('css-custom-properties.css'); // + apply some application enhancements // using the custom properties }

Slide 23

Slide 23 text

Current constraints Custom CSS Properties by default are: not typed in result - not animatable

Slide 24

Slide 24 text

Why animation is important

Slide 25

Slide 25 text

Why typed CSS properties and values are important support and validation in IDE syntax highlight performance Browser support DevTools support linters, compilers...

Slide 26

Slide 26 text

Introducing CSS property types? Property Value definition field Example value text-align left | right | center | justify center padding-top | 5% border-width [ | thick | medium | thin ]{1,4} 2px medium 4px

Slide 27

Slide 27 text

Houdini group CSS Properties & Values API CSS Typed OM CSS Parser API Font Metrics API Worklets CSS Layout API CSS Paint API CSS Animation Worklet API CSS Properties & Values API CSS Typed OM

Slide 28

Slide 28 text

CSS Typed OM spec // CSS -> JS const map = document.querySelector('.example').styleMap; console.log( map.get('font-size') ); // CSSSimpleLength {value: 12, type: "px", cssText: "12px"} // JS -> JS console.log( new CSSUnitValue(5, "px") ); // CSSUnitValue{value:5,unit:"px",type:"length",cssText:"5px"} // JS -> CSS // set style "transform: translate3d(0px, -72.0588%, 0px);" elem.outputStyleMap.set('transform', new CSSTransformValue([ new CSSTranslation( 0, new CSSSimpleLength(100 - currentPercent, '%'), 0 )])); behind the “Experimental Web Platform features” flag in

Slide 29

Slide 29 text

CSS Properties and Values API CSS.registerProperty({ name: "--stop-color", syntax: "", inherits: false, initialValue: "black" });

Slide 30

Slide 30 text

Why it's important Without With

Slide 31

Slide 31 text

"syntax" of CSS properties Default: "*" Supported Values: "" "" "" "" "" "" "" "" "" "

Slide 32

Slide 32 text

And... CSS Custom Properties should be animatable since they are provided with types? Let's try!

Slide 33

Slide 33 text

Native CSS Animation

Slide 34

Slide 34 text

Web animation API element.animate([ {cssProperty: 'fromValue'}, {cssProperty: 'toValue'} ], { duration: timeInMs, fill: 'none|forwards|backwards|both', delay: delayInMs, easing: 'linear|easy-in|cubic-bezier()...', iterations: iterationCount|Infinity }); rabbit.animate( [ { transform: "translateX(0)" }, { transform: "translateX(115px)" } ], { duration: 1000, // ms fill: "forwards", // stay at the end easing: "easy-in-out" } ); @keyframes rabbitMove { 0% { transform: translateX(0); } 100% { transform: translateX(115px); } } .rabbit { animation: rabbitMove 1s ease-in-out; animation-fill-mode: forwards; }

Slide 35

Slide 35 text

Native JavaScript animation

Slide 36

Slide 36 text

CSS/JS/Browser CSS JavaScript Browser CSS Custom Properties CSS Property Types Typed OM API CSSOM ?

Slide 37

Slide 37 text

Browser rendering Pixel rendering pipeline Internal browser engine

Slide 38

Slide 38 text

Worklets Houdini’s goal is to expose browser APIs to allow web developers to hook up their own code into the CSS engine. It’s probably not unrealistic to assume that some of these code fragments will have to be run every. single. frame. - similar to Web and Service Workers - have a separate thread - don't interact with DOM directly - limited API -> very performant - use the JS additions, essentially- ECMAScript 2015+ Classes with named methods - triggered when needed and possible

Slide 39

Slide 39 text

image credit

Slide 40

Slide 40 text

Paint Worklet The paint stage of CSS is responsible for painting the background, content and highlight of a box (based on the box’s size and computed style). The API allows to paint a part of a box in response to size / computed style changes with an additional function. - background-image - border-image - list-style-image - content - cursor Custom image can be paint on every browser paint action. Applicable for:

Slide 41

Slide 41 text

Paint Worklet example /* CSS */ .multi-border {--border-top-width: 10; border-image: paint(border-colors);} // JS CSS.registerProperty({ name: '--border-top-width', syntax: '', inherits: false, initialValue: '0', }); // add a Worklet paintWorklet.addModule('border-colors.js'); // WORKLET "border-colors.js" registerPaint('border-colors', class BorderColors { static get inputProperties() { return ['--border-top-width']; } paint(ctx, size, styleMap) { const elWidth = size.width; const topWidth = styleMap.get('--border-top-width').value; // draw a border ctx.fillStyle = 'magenta'; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(elWidth, 0); ctx.lineTo(elWidth, topWidth); ctx.lineTo(0, topWidth); ctx.lineTo(0, 0); ctx.fill(); } });

Slide 42

Slide 42 text

Multiple border polyfill using Paint Worklet

Slide 43

Slide 43 text

Browsers are smart and trigger paint only when and where needed ( ) demo

Slide 44

Slide 44 text

Layout Worklet - to create own layout algorithms - having access to set constraints, behaviour, boundaries - interact blocks, fragments and even text /* CSS */ .center { display: layout(centering); } CSS Layout API

Slide 45

Slide 45 text

/* CSS */ .photos { display: layout(masonry); } // JS (layout worklet) registerLayout('masonry', class extends Layout { *layout(/*...*/) {/*...*/} });

Slide 46

Slide 46 text

Animation Worklet Sticky Header implemented in the stable version of Chrome using Houdini task force achievements In-sync with the compositor thread on a “best effort” basis (tried on every frame, but may be as requestAnimationFrame) - animations and mostly scroll effects: - sticky elements - smooth scroll animations - scroll up bar Use cases is available Polyfill

Slide 47

Slide 47 text

Compositor only properties Pixel rendering pipeline Changing does not trigger any geometry changes or painting Carried out by the compositor thread with the help of the GPU. Property changes which can be handled by the compositor alone opacity transform

Slide 48

Slide 48 text

Scroll position indicator using Animation worklet

Slide 49

Slide 49 text

Animation Worklet- CSS/JS parts /* CSS */ .scroll-position { /* shifted to the left */ left: -100%; /* CSS animator directive to link up elements to an animator instance */ --animator: scroll-position-worklet scrollerElementReference; } /* JS (add a Worklet module) */ animationWorklet.addModule('worklet.js');

Slide 50

Slide 50 text

Animation Worklet itself /* Animators are classes that are run in the worklet and get to control certain attributes of DOM elements. */ registerAnimator('scroll-position-worklet', class ScrollPositionAnimator { static get elements() { return [{ // linked element name name: 'scrollerElementReference', // properties the animator needs to read to compute the animation // animator can be skipped if input props not changed since last frame inputProperties: [], // Output properties are properties that the animator might mutate // "opacity" and/or "transform" outputProperties: ['transform'] }] }; static get timelines() { // timeline options list }; animate(elementMap, timelines) { // Animation frame logic goes here }; }

Slide 51

Slide 51 text

Timeline and animate registerAnimator('scroll-position-worklet', class { // listen global vertical scroll static get timelines() { return [{type: 'scroll', options: {orientation: 'vertical'}}] }; animate(elementMap, timelines) { // current scroll position in range [0-100%] (of page) const scrollPosition = parseFloat(timelines[0].currentTime) * 100; elementMap.get('scrollerElementReference').forEach(elem => { // set CSS "transform:translate3d(`${scrollPosition}%`, 0, 0)"; elem.outputStyleMap.set('transform', new CSSTransformValue([ new CSSTranslation( new CSSSimpleLength(scrollPosition, '%'),0,0 )])); }); }}

Slide 52

Slide 52 text

Animation Worklet result

Slide 53

Slide 53 text

"endScrollOffset" option static get timelines() { return [{type: 'scroll', options: { orientation: 'vertical', // set scroll position to 100% at 375px endScrollOffset: '375px' }}]};

Slide 54

Slide 54 text

New Twitter Header effect using Animation Worklet

Slide 55

Slide 55 text

Consclusions Bright Reality - - (when available) for performance improvements and progressive enhancement Bright Future - experiment with using the - play with in Chrome - stay tuned with and ( , , , API etc.) start using CSS Custom Properties register Custom Properties from JS Animation Worklet polyfill Paint Worklet CSS Houdini Group specs Worklets Layout Font Metrics Typed OM

Slide 56

Slide 56 text

Thank you! Serg Hospodarets @malyw