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

CSS Houdini- the bridge between CSS, JavaScript...

Serg Hospodarets
May 17, 2017
140

CSS Houdini- the bridge between CSS, JavaScript and the browser

Live slides + Demos: http://slides.com/malyw/houdini-codemotion#/

Today CSS Custom Properties are supported in all the major browsers. Now it’s time to do the next step- to have an ability to register new Custom Properties from JavaScript and setup the browser how to work with them (e.g. real CSS polyfills). They should work with the same performance as the native CSS properties, being animatable and aligned with CSSOM. Custom Properties can be used as a bridge between CSS and JavaScript. Houdini Task force introduces specs and JavaScript Worklets to expose the interaction with previously fully internal browser rendering mechanisms (during Paint, Layout, Composite stages). All this brings Front-End development to the next level, parts of which are already available for the developers.

Serg Hospodarets

May 17, 2017
Tweet

Transcript

  1. 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; }
  2. 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
  3. Native CSS Syntax /* declaration */ --VAR_NAME: <declaration-value>; /* usage

    */ var(--VAR_NAME) /* root element selector (global scope) */ /* usually <html> */ :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
  4. 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; }
  5. 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); }
  6. 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 */ }
  7. 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); }
  8. 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); }
  9. 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; }
  10. 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; } <!-- HTML --> <div class="text">.text</div> <div class="text active">.text.active</div>
  11. 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...
  12. 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 */ } <!-- HTML (in case of older browsers support) --> <link href="without-css-custom-properties.css" rel="stylesheet" type="text/css" media="all" /> <script> if(isSupported){ removeCss('without-css-custom-properties.css'); loadCss('css-custom-properties.css'); // + apply some application enhancements // using the custom properties } </script>
  13. Why typed CSS properties and values are important support and

    validation in IDE syntax highlight performance Browser support DevTools support linters, compilers...
  14. Introducing CSS property types? Property Value definition field Example value

    text-align left | right | center | justify center padding-top <length> | <percentage> 5% border-width [ <length> | thick | medium | thin ]{1,4} 2px medium 4px
  15. 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
  16. 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
  17. "syntax" of CSS properties Default: "*" Supported Values: "<length>" "<number>"

    "<percentage>" "<length-percentage>" "<color>" "<image>" "<url>" "<integer>" "<angle>" "<time>" "<resolution>" "<transform-function>" Examples: "<length> | <percentage>" both, but not calc() combinations "<length-percentage>" both + calc() combinations of both types "big | bigger" accepts either value "<length>+" accepts a list of length values
  18. 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; }
  19. 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
  20. 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 <image> function. - background-image - border-image - list-style-image - content - cursor Custom image can be paint on every browser paint action. Applicable for:
  21. Paint Worklet example /* CSS */ .multi-border {--border-top-width: 10; border-image:

    paint(border-colors);} // JS CSS.registerProperty({ name: '--border-top-width', syntax: '<number>', 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(); } });
  22. 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
  23. /* CSS */ .photos { display: layout(masonry); } // JS

    (layout worklet) registerLayout('masonry', class extends Layout { *layout(/*...*/) {/*...*/} });
  24. 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
  25. 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
  26. 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');
  27. 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 }; }
  28. 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 )])); }); }}
  29. "endScrollOffset" option static get timelines() { return [{type: 'scroll', options:

    { orientation: 'vertical', // set scroll position to 100% at 375px endScrollOffset: '375px' }}]};
  30. 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