@una

The Magic of CSS Houdini

Confront | @una

Thank you for bringing us together!

Una Kravets πŸ¦„

@una

In this talk:

  1. CSS Today (Wins & Limitations)
  2. What is Houdini
  3. Houdini CSS Worklets and Examples
  4. ✨Sparkle Time✨

Writing CSS 15 Years Ago

CSS Grid πŸ—


Hello World
Other Content Here

.grid-area {
  display: grid;
  grid-template:
    "πŸ˜„ πŸ˜„ πŸ˜„ πŸ˜„" 4rem
    "↕️ πŸ–Ό πŸ–Ό πŸ–Ό" 400px
    "↕️ πŸ… πŸ… πŸ…" auto
    "πŸ‘Ÿ πŸ‘Ÿ πŸ‘Ÿ πŸ‘Ÿ" 100px /
    10% 30% 30% 30%
}

.header {
  grid-area: πŸ˜„;
  background-color: hotpink;
}

.sidebar {
  grid-area: ↕️;
  background-color: skyblue;
}

.gallery {
  grid-area: πŸ–Ό;
  background-color: yellow;
}

.tomato {
  grid-area: πŸ…;
  background: tomato;
}

.footer {
  grid-area: πŸ‘Ÿ;
  background-color: dimgray;
}

/* Unrelated */

body {
  font-family: monospace;
  font-size: 32px;
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.grid-area div {
  padding: 1rem;
  text-align: center;
}

.gallery {
  font-size: 80px;
}

CSS Custom Properties 🎨


Hello, Confront!


:root {
  --primaryColor: hotpink;
  --background: wheat;
}

body {
  background-color: var(--background);
}

h1 {
  color: var(--primaryColor);
}

const changeBg = (color) => { 
  document.documentElement.style.setProperty('--background', color)
}

//onmousemove = (e) => { 
//  console.log("mouse location:", e.clientX, e.clientY)
//  changeBg(`rgb(${e.clientX}, 50, 50)`)
//}



//onmousemove = (e) => { 
//  let valX = (e.clientX / document.body.clientWidth) * 255
//  let valY = (e.clientY / document.body.clientHeight) * 255
//  console.log(valX, valY)
//  changeBg(`rgb(${valX}, ${valY}, 50)`)
//}

Form Styling

Form Styling 🚫

CSS Gradients βœ…

Conic Gradients 🚫

Animated Gradients 🚫

Custom Borders πŸ€·πŸ»β€β™€οΈ

developer.mozilla.org/docs/Web/CSS/border

Non-Standard Borders 🚫

developer.mozilla.org/docs/Web/CSS/border

Houdini!!

Houdini!?

Houdini is a low-level API for CSS
smashingmagazine.com/2016/03/houdini-maybe-the-most-exciting-development-in-css-youve-never-heard-of/
vea.re/blog/making-your-website-fast-and-offline-available-with-a-service-worker

The Houdini Project is About User-Extensible CSS

JS in CSS?!

Properties & Values

drafts.css-houdini.org/css-properties-values-api/
CSS Variables = CSS Custom Properties
CSS Custom Properties++
CSS.registerProperty({ name: "--my-color", syntax: "<color>", initialValue: "black", inherits: false }); drafts.css-houdini.org/css-properties-values-api/


.box {
  --color: red;
  background: linear-gradient(var(--color), deeppink);
  transition: --color 1s;
  width: 50vw;
  height: 90vh;
  display: block;
  border: 1px solid black;
  margin: 0 auto;
}

.box:hover {
  --color: blue;
}

CSS.registerProperty({ 
  name: '--color-stop', 
  syntax: '<color>', 
  inherits: false, 
  initialValue: 'transparent'
})
css-houdini.rocks/animating-gradient

Current Syntax's

  • "<length>": Any valid <length> value
  • "<number>": <number> values
  • "<percentage>": Any valid <percentage> value
  • "<length-percentage>": Any valid <length> or <percentage> value, any valid <calc()> expression combining <length> and <percentage> components.
  • "<color>": Any valid <color> value
  • "<image>": Any valid <image> value
  • "<url>": Any valid <url> value
  • "<integer>": Any valid <integer> value
  • "<angle>": Any valid <angle> value
  • "<time>": Any valid <time> value
  • "<resolution>": Any valid <resolution> value
  • "<transform-function>": Any valid <transform-function> value
  • "<custom-ident>":Any valid <custom-ident> value
https://drafts.css-houdini.org/css-properties-values-api/ | 22 September 2018

        
.box {
  background: var(--colorPrimary);
  transition-duration: 1s;
  width: 50vw;
  height: 90vh;
  display: block;
  border: 1px solid black;
  margin: 0 auto;
}

.box:hover {
  background: var(--colorSecondary);
}
        

CSS.registerProperty({ 
  name: '--colorPrimary', 
  syntax: '<color>', 
  inherits: false, 
  initialValue: 'blueviolet'
})
CSS.registerProperty({ 
  name: '--colorSecondary', 
  syntax: '<color>', 
  inherits: false, 
  initialValue: 'deeppink'
})

"Typed CSS Variables"

Typed OM

w3.org/TR/css-typed-om-1/
Current CSSOM is String Based
"This specification introduces a new way to interact with underlying values, by representing them with specialized JS objects that can be manipulated and understood more easily and more reliably than string parsing/concatenation" w3.org/TR/css-typed-om-1
CSS values ➑️ typed JavaScript objects
comment \/\*[^*]*\*+([^/*][^*]*\*+)*\/ num [0-9]+|[0-9]*"."[0-9]+ nonascii [\200-\377] nmstart [_a-z]|{nonascii}|{escape} nmchar [_a-z0-9-]|{nonascii}|{escape} name {nmchar}+ ident {nmstart}{nmchar}* ruleset : selector [ ',' S* selector ]* '{' S* declaration [ ';' S* declaration ]* '}' S* ; selector : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]? ; simple_selector : element_name [ HASH | class | attrib | pseudo ]* | [ HASH | class | attrib | pseudo ]+ ; class : '.' IDENT ; element_name : IDENT | '*' ; attrib : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ] ']' ; pseudo : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ] ;
html5rocks.com/en/tutorials/internals/howbrowserswork
html5rocks.com/en/tutorials/internals/howbrowserswork

Current CSS:

element.attributeStyleMap.get('height') //returns string

Houdini Typed OM:

element.computedStyleMap().get('height') //returns CSSUnitValue

github.com/w3c/css-houdini-drafts/blob/master/css-typed-om/README.md

Paint Worklet


Awesome Mockup

This uses placeholder boxes created with the CSS Paint API. Neat! You can resize them by dragging their bottom right corners.


if (typeof registerPaint !== 'undefined') {
  class PlaceholderBoxPainter {
    paint(ctx, size) {
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#666';

      // draw line from top left to bottom right
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(size.width, size.height);
      ctx.stroke();

      // draw line from top right to bottom left
      ctx.beginPath();
      ctx.moveTo(size.width, 0);
      ctx.lineTo(0, size.height);
      ctx.stroke();
    }
  }

  registerPaint('placeholder-box', PlaceholderBoxPainter);
}
  

.placeholder {
  background-image: paint(placeholder-box);

  background-color: #fff;
  max-width: 100%;
  border: 2px solid #666;
  overflow: auto;
  resize: both;
}

header {
  width: 600px;
  height: 100px;
  margin: 0 auto;
}

.content-wrapper {
  display: flex;
  width: 600px;
  margin: 40px auto 0;
  font-size: 1.25rem;
  line-height: 1;
}

main {
  width: 440px;
  font-family: 'Dank Mono';
}

main .placeholder {
  float: left;
  width: 100px;
  height: 100px;
  margin: 10px;
}

aside {
  width: 140px;
  margin-left: 20px;
}

aside .placeholder {
  width: 140px;
  height: 320px;
  margin: 0 auto;
}     
  
by Will Boyd: codepen.io/lonekorean/pen/wmwJQX

  

Hello World!



What a Wonderful Life! I Can Keep Going!



Lorem ipsum dolor sit amet consectetur, adipisicing elit. Excepturi, aliquid nemo earum perspiciatis harum nihil sint expedita labore, architecto maxime in error officia iusto iure.

Alias similique error vero ab!Minus ad provident a omnis, porro eum iste quia at eaque vel quidem neque dolorum? Aliquam provident rem id in repellat impedit voluptatum tempora, odit vero eaque dolorem, cum voluptatibus?

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Excepturi, aliquid nemo earum perspiciatis harum nihil sint expedita labore, architecto maxime in error officia iusto iure.

Alias similique error vero ab!Minus ad provident a omnis, porro eum iste quia at eaque vel quidem neque dolorum? Aliquam provident rem id in repellat impedit voluptatum tempora, odit vero eaque dolorem, cum voluptatibus?


  if (typeof registerPaint !== 'undefined') {
    class Rainbowtize {
      paint(ctx, size) {
        const w = size.width;
        const h = size.height;
        
        for (var i = 0; i < h; i++) {
          for (var j = 0; j < w; j++) {
            ctx.fillStyle = 'rgb(' + 
              Math.floor(255 - 20 * i) + ', ' +   
              Math.floor(255 - 20 * j) + ', 200)';
            ctx.fillRect(j * 25, i * 25, 25, 25);
          }
        }
      }
    }
  
    registerPaint('rainbowtize', Rainbowtize);
  }
  

  .speech {
    background-image: paint(rainbowtize);
    padding: 1em;
    display: inline-block;
    border-radius: 20px;
    font-family: 'Dank Mono'
  }
  

  

const window = {}

// TinyColor v1.4.1
// https://github.com/bgrins/TinyColor
// 2016-07-07, Brian Grinstead, MIT License
!function(a){function b(a,d){if(a=a?a:"",d=d||{},a instanceof b)return a;if(!(this instanceof b))return new b(a,d);var e=c(a);this._originalInput=a,this._r=e.r,this._g=e.g,this._b=e.b,this._a=e.a,this._roundA=P(100*this._a)/100,this._format=d.format||e.format,this._gradientType=d.gradientType,this._r<1&&(this._r=P(this._r)),this._g<1&&(this._g=P(this._g)),this._b<1&&(this._b=P(this._b)),this._ok=e.ok,this._tc_id=O++}function c(a){var b={r:0,g:0,b:0},c=1,e=null,g=null,i=null,j=!1,k=!1;return"string"==typeof a&&(a=K(a)),"object"==typeof a&&(J(a.r)&&J(a.g)&&J(a.b)?(b=d(a.r,a.g,a.b),j=!0,k="%"===String(a.r).substr(-1)?"prgb":"rgb"):J(a.h)&&J(a.s)&&J(a.v)?(e=G(a.s),g=G(a.v),b=h(a.h,e,g),j=!0,k="hsv"):J(a.h)&&J(a.s)&&J(a.l)&&(e=G(a.s),i=G(a.l),b=f(a.h,e,i),j=!0,k="hsl"),a.hasOwnProperty("a")&&(c=a.a)),c=z(c),{ok:j,format:a.format||k,r:Q(255,R(b.r,0)),g:Q(255,R(b.g,0)),b:Q(255,R(b.b,0)),a:c}}function d(a,b,c){return{r:255*A(a,255),g:255*A(b,255),b:255*A(c,255)}}function e(a,b,c){a=A(a,255),b=A(b,255),c=A(c,255);var d,e,f=R(a,b,c),g=Q(a,b,c),h=(f+g)/2;if(f==g)d=e=0;else{var i=f-g;switch(e=h>.5?i/(2-f-g):i/(f+g),f){case a:d=(b-c)/i+(c>b?6:0);break;case b:d=(c-a)/i+2;break;case c:d=(a-b)/i+4}d/=6}return{h:d,s:e,l:h}}function f(a,b,c){function d(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+6*(b-a)*(2/3-c):a}var e,f,g;if(a=A(a,360),b=A(b,100),c=A(c,100),0===b)e=f=g=c;else{var h=.5>c?c*(1+b):c+b-c*b,i=2*c-h;e=d(i,h,a+1/3),f=d(i,h,a),g=d(i,h,a-1/3)}return{r:255*e,g:255*f,b:255*g}}function g(a,b,c){a=A(a,255),b=A(b,255),c=A(c,255);var d,e,f=R(a,b,c),g=Q(a,b,c),h=f,i=f-g;if(e=0===f?0:i/f,f==g)d=0;else{switch(f){case a:d=(b-c)/i+(c>b?6:0);break;case b:d=(c-a)/i+2;break;case c:d=(a-b)/i+4}d/=6}return{h:d,s:e,v:h}}function h(b,c,d){b=6*A(b,360),c=A(c,100),d=A(d,100);var e=a.floor(b),f=b-e,g=d*(1-c),h=d*(1-f*c),i=d*(1-(1-f)*c),j=e%6,k=[d,h,g,g,i,d][j],l=[i,d,d,h,g,g][j],m=[g,g,i,d,d,h][j];return{r:255*k,g:255*l,b:255*m}}function i(a,b,c,d){var e=[F(P(a).toString(16)),F(P(b).toString(16)),F(P(c).toString(16))];return d&&e[0].charAt(0)==e[0].charAt(1)&&e[1].charAt(0)==e[1].charAt(1)&&e[2].charAt(0)==e[2].charAt(1)?e[0].charAt(0)+e[1].charAt(0)+e[2].charAt(0):e.join("")}function j(a,b,c,d,e){var f=[F(P(a).toString(16)),F(P(b).toString(16)),F(P(c).toString(16)),F(H(d))];return e&&f[0].charAt(0)==f[0].charAt(1)&&f[1].charAt(0)==f[1].charAt(1)&&f[2].charAt(0)==f[2].charAt(1)&&f[3].charAt(0)==f[3].charAt(1)?f[0].charAt(0)+f[1].charAt(0)+f[2].charAt(0)+f[3].charAt(0):f.join("")}function k(a,b,c,d){var e=[F(H(d)),F(P(a).toString(16)),F(P(b).toString(16)),F(P(c).toString(16))];return e.join("")}function l(a,c){c=0===c?0:c||10;var d=b(a).toHsl();return d.s-=c/100,d.s=B(d.s),b(d)}function m(a,c){c=0===c?0:c||10;var d=b(a).toHsl();return d.s+=c/100,d.s=B(d.s),b(d)}function n(a){return b(a).desaturate(100)}function o(a,c){c=0===c?0:c||10;var d=b(a).toHsl();return d.l+=c/100,d.l=B(d.l),b(d)}function p(a,c){c=0===c?0:c||10;var d=b(a).toRgb();return d.r=R(0,Q(255,d.r-P(255*-(c/100)))),d.g=R(0,Q(255,d.g-P(255*-(c/100)))),d.b=R(0,Q(255,d.b-P(255*-(c/100)))),b(d)}function q(a,c){c=0===c?0:c||10;var d=b(a).toHsl();return d.l-=c/100,d.l=B(d.l),b(d)}function r(a,c){var d=b(a).toHsl(),e=(d.h+c)%360;return d.h=0>e?360+e:e,b(d)}function s(a){var c=b(a).toHsl();return c.h=(c.h+180)%360,b(c)}function t(a){var c=b(a).toHsl(),d=c.h;return[b(a),b({h:(d+120)%360,s:c.s,l:c.l}),b({h:(d+240)%360,s:c.s,l:c.l})]}function u(a){var c=b(a).toHsl(),d=c.h;return[b(a),b({h:(d+90)%360,s:c.s,l:c.l}),b({h:(d+180)%360,s:c.s,l:c.l}),b({h:(d+270)%360,s:c.s,l:c.l})]}function v(a){var c=b(a).toHsl(),d=c.h;return[b(a),b({h:(d+72)%360,s:c.s,l:c.l}),b({h:(d+216)%360,s:c.s,l:c.l})]}function w(a,c,d){c=c||6,d=d||30;var e=b(a).toHsl(),f=360/d,g=[b(a)];for(e.h=(e.h-(f*c>>1)+720)%360;--c;)e.h=(e.h+f)%360,g.push(b(e));return g}function x(a,c){c=c||6;for(var d=b(a).toHsv(),e=d.h,f=d.s,g=d.v,h=[],i=1/c;c--;)h.push(b({h:e,s:f,v:g})),g=(g+i)%1;return h}function y(a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[a[c]]=c);return b}function z(a){return a=parseFloat(a),(isNaN(a)||0>a||a>1)&&(a=1),a}function A(b,c){D(b)&&(b="100%");var d=E(b);return b=Q(c,R(0,parseFloat(b))),d&&(b=parseInt(b*c,10)/100),a.abs(b-c)<1e-6?1:b%c/parseFloat(c)}function B(a){return Q(1,R(0,a))}function C(a){return parseInt(a,16)}function D(a){return"string"==typeof a&&-1!=a.indexOf(".")&&1===parseFloat(a)}function E(a){return"string"==typeof a&&-1!=a.indexOf("%")}function F(a){return 1==a.length?"0"+a:""+a}function G(a){return 1>=a&&(a=100*a+"%"),a}function H(b){return a.round(255*parseFloat(b)).toString(16)}function I(a){return C(a)/255}function J(a){return!!V.CSS_UNIT.exec(a)}function K(a){a=a.replace(M,"").replace(N,"").toLowerCase();var b=!1;if(T[a])a=T[a],b=!0;else if("transparent"==a)return{r:0,g:0,b:0,a:0,format:"name"};var c;return(c=V.rgb.exec(a))?{r:c[1],g:c[2],b:c[3]}:(c=V.rgba.exec(a))?{r:c[1],g:c[2],b:c[3],a:c[4]}:(c=V.hsl.exec(a))?{h:c[1],s:c[2],l:c[3]}:(c=V.hsla.exec(a))?{h:c[1],s:c[2],l:c[3],a:c[4]}:(c=V.hsv.exec(a))?{h:c[1],s:c[2],v:c[3]}:(c=V.hsva.exec(a))?{h:c[1],s:c[2],v:c[3],a:c[4]}:(c=V.hex8.exec(a))?{r:C(c[1]),g:C(c[2]),b:C(c[3]),a:I(c[4]),format:b?"name":"hex8"}:(c=V.hex6.exec(a))?{r:C(c[1]),g:C(c[2]),b:C(c[3]),format:b?"name":"hex"}:(c=V.hex4.exec(a))?{r:C(c[1]+""+c[1]),g:C(c[2]+""+c[2]),b:C(c[3]+""+c[3]),a:I(c[4]+""+c[4]),format:b?"name":"hex8"}:(c=V.hex3.exec(a))?{r:C(c[1]+""+c[1]),g:C(c[2]+""+c[2]),b:C(c[3]+""+c[3]),format:b?"name":"hex"}:!1}function L(a){var b,c;return a=a||{level:"AA",size:"small"},b=(a.level||"AA").toUpperCase(),c=(a.size||"small").toLowerCase(),"AA"!==b&&"AAA"!==b&&(b="AA"),"small"!==c&&"large"!==c&&(c="small"),{level:b,size:c}}var M=/^\s+/,N=/\s+$/,O=0,P=a.round,Q=a.min,R=a.max,S=a.random;b.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var a=this.toRgb();return(299*a.r+587*a.g+114*a.b)/1e3},getLuminance:function(){var b,c,d,e,f,g,h=this.toRgb();return b=h.r/255,c=h.g/255,d=h.b/255,e=.03928>=b?b/12.92:a.pow((b+.055)/1.055,2.4),f=.03928>=c?c/12.92:a.pow((c+.055)/1.055,2.4),g=.03928>=d?d/12.92:a.pow((d+.055)/1.055,2.4),.2126*e+.7152*f+.0722*g},setAlpha:function(a){return this._a=z(a),this._roundA=P(100*this._a)/100,this},toHsv:function(){var a=g(this._r,this._g,this._b);return{h:360*a.h,s:a.s,v:a.v,a:this._a}},toHsvString:function(){var a=g(this._r,this._g,this._b),b=P(360*a.h),c=P(100*a.s),d=P(100*a.v);return 1==this._a?"hsv("+b+", "+c+"%, "+d+"%)":"hsva("+b+", "+c+"%, "+d+"%, "+this._roundA+")"},toHsl:function(){var a=e(this._r,this._g,this._b);return{h:360*a.h,s:a.s,l:a.l,a:this._a}},toHslString:function(){var a=e(this._r,this._g,this._b),b=P(360*a.h),c=P(100*a.s),d=P(100*a.l);return 1==this._a?"hsl("+b+", "+c+"%, "+d+"%)":"hsla("+b+", "+c+"%, "+d+"%, "+this._roundA+")"},toHex:function(a){return i(this._r,this._g,this._b,a)},toHexString:function(a){return"#"+this.toHex(a)},toHex8:function(a){return j(this._r,this._g,this._b,this._a,a)},toHex8String:function(a){return"#"+this.toHex8(a)},toRgb:function(){return{r:P(this._r),g:P(this._g),b:P(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+P(this._r)+", "+P(this._g)+", "+P(this._b)+")":"rgba("+P(this._r)+", "+P(this._g)+", "+P(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:P(100*A(this._r,255))+"%",g:P(100*A(this._g,255))+"%",b:P(100*A(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+P(100*A(this._r,255))+"%, "+P(100*A(this._g,255))+"%, "+P(100*A(this._b,255))+"%)":"rgba("+P(100*A(this._r,255))+"%, "+P(100*A(this._g,255))+"%, "+P(100*A(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":this._a<1?!1:U[i(this._r,this._g,this._b,!0)]||!1},toFilter:function(a){var c="#"+k(this._r,this._g,this._b,this._a),d=c,e=this._gradientType?"GradientType = 1, ":"";if(a){var f=b(a);d="#"+k(f._r,f._g,f._b,f._a)}return"progid:DXImageTransform.Microsoft.gradient("+e+"startColorstr="+c+",endColorstr="+d+")"},toString:function(a){var b=!!a;a=a||this._format;var c=!1,d=this._a<1&&this._a>=0,e=!b&&d&&("hex"===a||"hex6"===a||"hex3"===a||"hex4"===a||"hex8"===a||"name"===a);return e?"name"===a&&0===this._a?this.toName():this.toRgbString():("rgb"===a&&(c=this.toRgbString()),"prgb"===a&&(c=this.toPercentageRgbString()),("hex"===a||"hex6"===a)&&(c=this.toHexString()),"hex3"===a&&(c=this.toHexString(!0)),"hex4"===a&&(c=this.toHex8String(!0)),"hex8"===a&&(c=this.toHex8String()),"name"===a&&(c=this.toName()),"hsl"===a&&(c=this.toHslString()),"hsv"===a&&(c=this.toHsvString()),c||this.toHexString())},clone:function(){return b(this.toString())},_applyModification:function(a,b){var c=a.apply(null,[this].concat([].slice.call(b)));return this._r=c._r,this._g=c._g,this._b=c._b,this.setAlpha(c._a),this},lighten:function(){return this._applyModification(o,arguments)},brighten:function(){return this._applyModification(p,arguments)},darken:function(){return this._applyModification(q,arguments)},desaturate:function(){return this._applyModification(l,arguments)},saturate:function(){return this._applyModification(m,arguments)},greyscale:function(){return this._applyModification(n,arguments)},spin:function(){return this._applyModification(r,arguments)},_applyCombination:function(a,b){return a.apply(null,[this].concat([].slice.call(b)))},analogous:function(){return this._applyCombination(w,arguments)},complement:function(){return this._applyCombination(s,arguments)},monochromatic:function(){return this._applyCombination(x,arguments)},splitcomplement:function(){return this._applyCombination(v,arguments)},triad:function(){return this._applyCombination(t,arguments)},tetrad:function(){return this._applyCombination(u,arguments)}},b.fromRatio=function(a,c){if("object"==typeof a){var d={};for(var e in a)a.hasOwnProperty(e)&&(d[e]="a"===e?a[e]:G(a[e]));a=d}return b(a,c)},b.equals=function(a,c){return a&&c?b(a).toRgbString()==b(c).toRgbString():!1},b.random=function(){return b.fromRatio({r:S(),g:S(),b:S()})},b.mix=function(a,c,d){d=0===d?0:d||50;var e=b(a).toRgb(),f=b(c).toRgb(),g=d/100,h={r:(f.r-e.r)*g+e.r,g:(f.g-e.g)*g+e.g,b:(f.b-e.b)*g+e.b,a:(f.a-e.a)*g+e.a};return b(h)},b.readability=function(c,d){var e=b(c),f=b(d);return(a.max(e.getLuminance(),f.getLuminance())+.05)/(a.min(e.getLuminance(),f.getLuminance())+.05)},b.isReadable=function(a,c,d){var e,f,g=b.readability(a,c);switch(f=!1,e=L(d),e.level+e.size){case"AAsmall":case"AAAlarge":f=g>=4.5;break;case"AAlarge":f=g>=3;break;case"AAAsmall":f=g>=7}return f},b.mostReadable=function(a,c,d){var e,f,g,h,i=null,j=0;d=d||{},f=d.includeFallbackColors,g=d.level,h=d.size;for(var k=0;kj&&(j=e,i=b(c[k]));return b.isReadable(a,i,{level:g,size:h})||!f?i:(d.includeFallbackColors=!1,b.mostReadable(a,["#fff","#000"],d))};var T=b.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},U=b.hexNames=y(T),V=function(){var a="[-\\+]?\\d+%?",b="[-\\+]?\\d*\\.\\d+%?",c="(?:"+b+")|(?:"+a+")",d="[\\s|\\(]+("+c+")[,|\\s]+("+c+")[,|\\s]+("+c+")\\s*\\)?",e="[\\s|\\(]+("+c+")[,|\\s]+("+c+")[,|\\s]+("+c+")[,|\\s]+("+c+")\\s*\\)?";return{CSS_UNIT:new RegExp(c),rgb:new RegExp("rgb"+d),rgba:new RegExp("rgba"+e),hsl:new RegExp("hsl"+d),hsla:new RegExp("hsla"+e),hsv:new RegExp("hsv"+d),hsva:new RegExp("hsva"+e),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/}}();"undefined"!=typeof module&&module.exports?module.exports=b:"function"==typeof define&&define.amd?define(function(){return b}):window.tinycolor=b}(Math);

// ConicalGradient
// from: https://gist.github.com/aiboy/52715dfae7ca16da9f09
function ConicalGradient() {
    this._offsets = [];
    this._colors = [];
}

ConicalGradient.prototype = {

    addColorStop: function(offset, color) {
        this._offsets.push(offset);
        this._colors.push(color);
        return this;
    },

    _offsetsReverse: function(offset, index, array) {
        array[index] = 1 - offset;
    },

    fill: function(context, x, y, radius, startAngle, endAngle, anticlockwise) {
        var offsets = this._offsets;
        var colors = this._colors;
        
        var PI = Math.PI;
        var TWO_PI = PI * 2;

        if (startAngle < 0) startAngle = startAngle % TWO_PI + TWO_PI;
        startAngle %= TWO_PI;
        if (endAngle < 0) endAngle = endAngle % TWO_PI + TWO_PI;
        endAngle  %= TWO_PI;
        
        if (anticlockwise) {
            var swap   = startAngle;
            startAngle = endAngle;
            endAngle   = swap;
            
            colors.reverse();
            offsets.reverse();
            offsets.forEach(this._offsetsReverse);
        }

        if (
            startAngle > endAngle ||
            Math.abs(endAngle - startAngle) < 0.0001
        ) endAngle += TWO_PI;
        
        var colorsLength = colors.length;
        
        var currentColorIndex = 0;
        var currentColor = colors[currentColorIndex];
        var nextColor    = colors[currentColorIndex];
        
        var prevOffset = 0;
        var currentOffset = offsets[currentColorIndex];
        var offsetDist = currentOffset - prevOffset;

        var totalAngleDeg = (endAngle - startAngle) * 180 / PI;
        var stepAngleRad = (endAngle - startAngle) / totalAngleDeg;

        var arcStartAngle = startAngle;
        var arcEndAngle;

        var r1 = currentColor[0], g1 = currentColor[1], b1 = currentColor[2], a1 = currentColor[3];
        var r2 = nextColor[0],    g2 = nextColor[1],    b2 = nextColor[2],    a2 = nextColor[3];
        if (!a1 && a1 !== 0) a1 = 1;
        if (!a2 && a2 !== 0) a2 = 1;
        var rd = r2 - r1, gd = g2 - g1, bd = b2 - b1, ad = a2 - a1;
        var t, r, g, b, a;

        context.save();
        for (var i = 0, n = 1 / totalAngleDeg; i < 1; i += n) {
            if (i >= currentOffset) {
                currentColorIndex++;

                currentColor = nextColor;
                r1 = currentColor[0]; g1 = currentColor[1]; b1 = currentColor[2]; a1 = currentColor[3];
                if (!a1 && a1 !== 0) a1 = 1;

                nextColor = colors[currentColorIndex];
                r2 = nextColor[0]; g2 = nextColor[1]; b2 = nextColor[2]; a2 = nextColor[3];
                if (!a2 && a2 !== 0) a2 = 1;
                
                rd = r2 - r1; gd = g2 - g1; bd = b2 - b1; ad = a2 - a1;
                
                prevOffset = currentOffset;
                currentOffset = offsets[currentColorIndex];
                offsetDist = currentOffset - prevOffset;
            }
            
            t = (i - prevOffset) / offsetDist;
            r = (rd * t + r1) & 255;
            g = (gd * t + g1) & 255;
            b = (bd * t + b1) & 255;
            a = ad * t + a1;

            arcEndAngle = arcStartAngle + stepAngleRad;

            context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
            context.beginPath();
            context.moveTo(x, y);
            context.arc(x, y, radius, arcStartAngle - 0.02, arcEndAngle, false);
            context.closePath();
            context.fill();

            arcStartAngle += stepAngleRad;
        }
        context.restore();

        return this;
    }
};

registerPaint('conic-gradient', class {

  static get inputArguments() {
    return [
      '+'
    ]
  }

  paint(ctx, geom, props, args) {
    const conic = new ConicalGradient();
    // args no longer returns array of args
    const splittedArgs = args.toString().split(' ');
    splittedArgs.forEach((color, i) => {
      const c = window.tinycolor(color.toString()).toRgb();
      conic.addColorStop(i / (splittedArgs.length - 1), [c.r, c.g, c.b]);
    })
    conic.fill(ctx, geom.width / 2, geom.height / 2, geom.width, - Math.PI / 2, Math.PI * 2 - Math.PI / 2, false);
  }
});
  

.box {
  background: paint(conic-gradient, red magenta blue cyan lime yellow);
  width: 50vw;
  height: 90vh;
  display: block;
  border: 1px solid black;
  margin: 0 auto;
}

/* Future Syntax with Custom Functions

.box {
  background: --conic-gradient(red magenta blue cyan lime yellow);
}

*/
  
css-houdini.rocks

Layout Worklet

github.com/w3c/css-houdini-drafts
The CSS Layout API is designed to give web developers the ability to write their *own layout algorithms* in addition to the native algorithms user agents ship with today

registerLayout('sample-layout', class {
  // Properties to look for on calling element
  static get inputProperties() { return ['--foo']; }
  // Properties to look for on direct child elements
  static get childrenInputProperties() { return ['--bar']; }
  // Options for the Layout
  // `childDisplay` can be 'block' or 'normal'. 'block' is similar to children of flex and grid containers, 'normal'. Otherwise boxes won't be blockified
  // `sizing` can be 'block-like' or 'manual'. 'block-like' will make the Layout Constraints's inline size be fixed, and block size be calculated like border-box. Otherwise, it's just the calculated inlineSize and blockSize
  static get layoutOptions() {
    return {
      childDisplay: 'normal',
      sizing: 'block-like'
    };
  }

  // Generator functions instead of normal functions to support async/parallel layout engines
  // Determines how a box fits its content or fits in to our layout context
  *intrinsicSizes(children, edges, styleMap) {
    // children - Child elements of box being laid out
    // edges - Layout Edges of the box being
    // styleMap - Typed OM style map of box being laid out

    // Intrinsic sizes code goes here.
  }

  *layout(children, edges, constraints, styleMap, breakToken) {
    // children - Child elements of Parent Layout
    // edges - `LayoutEdges` of Parent Layout
    // constraints - `Layout Constraints` of Parent Layout
    // styleMap - Typed OM style map of Parent Layout
    // breakToken - Token (if paginating for printing for example) to resume layout at

    // Layout code goes here.
  }
});
houdini.glitch.me

A
B
C
D

registerLayout("random", class {
  *intrinsicSizes() {}
  
  *layout(children, edges, constraintSpace, styles) {
    const childFragments = []
    console.log(constraintSpace);

    for(const child of children) {
      const childFragment = yield child.layoutNextFragment();
      console.log(childFragment);
      childFragment.inlineOffset = Math.random() * constraintSpace.fixedInlineSize;
      childFragment.blockOffset = Math.random() * constraintSpace.fixedBlockSize;
      childFragments.push(childFragment);
    }
    return {
      childFragments
    };
  }
});

/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*     http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

main {
  width: 500px;
  height: 500px;
  outline: 1px solid black;
  display: layout(random);
}
div {
  width: 200px;
  height: 200px;
  background-color: hotpink;
}
  
github.com/GoogleChromeLabs/houdini-samples
Open Example in New Window

1 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret. Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret.
2 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret.
3 Lorem ipsum dolor sit amet.
4 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret.
5 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an.
6 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea.
7 Lorem ipsum dolor sit amet, consul disputando ne his, et vim accumsan ponderum. Rebum deseruisse ex vix. Vix stet honestatis definitionem an, et natum ocurreret cum, semper interpretaris cu mea. Eam saperet fierent luptatum no. Ius ei dicunt detracto elaboraret.

registerLayout('masonry', class {
  static get inputProperties() {
    return [ '--padding', '--columns' ];
  }

  *intrinsicSizes() { }
  *layout(children, edges, constraints, styleMap) {
    const inlineSize = constraints.fixedInlineSize;

    const padding = parseInt(styleMap.get('--padding').toString());
    const columnValue = styleMap.get('--columns').toString();

    // We also accept 'auto', which will select the BEST number of columns.
    let columns = parseInt(columnValue);
    if (columnValue == 'auto' || !columns) {
      columns = Math.ceil(inlineSize / 350); // MAGIC NUMBER \o/.
    }

    // Layout all children with simply their column size.
    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) {
      // Select the column with the least amount of stuff in it.
      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};
  }
});

/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*     http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

body {
  display: layout(masonry);
  --padding: 20;
  --columns: 3;
}

div {
  background-color: #9365ff;
  color: white;
  font: 16px monospace;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
  
github.com/GoogleChromeLabs/houdini-samples

Animation Worklet

"A new primitive for creating scroll-linked and other high performance procedural animations on the web"

wicg.github.io/animation-worklet

Web Animation API

  • Animation Timeline
  • Document & Effect Timing
  • Keyframe Model

Animation Worklet

  • Scroll-linked animations
  • Dedicated Threads (run parallel)
W3C
googlechromelabs.github.io/houdini-samples/animation-worklet/twitter-header/
googlechromelabs.github.io/houdini-samples/animation-worklet/spring-sticky/

Note: Today, the Animation API will only animate "fast" properties (like opacity and transform) and only items that can be composited

http://houdini.glitch.me/animation

✨Let's Make Magic✨

This is experimental

chrome://flags/#enable-experimental-web-platform-features

Caveats:

  • Must be https or localhost
  • (For current Houdini dev), must clear browser cache and refresh to see results

Check in JavaScript:

if ('paintWorklet' in CSS) { ... }

Check in CSS:

@supports (background: paint(something)) { ... }
1. Start with Canvas

See the Pen Sparkles -- Step 4 by Una Kravets (@una) on CodePen.

Don't Freak Out

This is a Really Exciting Time
to Be Building the Web

Thank You! πŸ’– @una

  • πŸ“ github.com/w3c/css-houdini-drafts
  • 🌈 css-houdini.rocks
  • πŸ‘¨πŸ»β€πŸ« houdini.glitch.me
  • ☁️ developers.google.com
  • πŸ‘©πŸ½β€πŸ”¬ googlechromelabs.github.io/houdini-samples
  • πŸ“• css-tricks.com/tag/houdini
  • πŸ¦‰ twitter.com/Una/lists/houdini