Part 1:
CSS Houdini is a W3C effort to define lower-level CSS DOM APIs for authors to understand, recreate, and extend high-level CSS authoring features.
definition: wiki.mozilla.org/CSS/Houdini
Why is Houdini being developed like this?
visit: houdini.glitch.me
Houdini Ingredients
@snugug
visit: extra.css
h1 {
--extra-confettiNumber: 30;
--extra-confettiLengthVariance: 15;
--extra-confettiWeightVariance: 4;
background: paint(extra-confetti);
}
watch: Custom Layout Walkthrough
el.attributeStyleMap.set('opacity', CSS.number(.5));
el.attributeStyleMap.get('opacity');
// CSSUnitValue {
value: 0.5,
unit: 'number'
};
attributeStyleMap
el.style.opacity = 0.3;
typeof el.style.opacity === 'string'
Current CSSOM
Typed OM
const cs = document.querySelector('.content').computedStyleMap();
cs.get('background-position').x;
// CSSUnitValue {
// value: 50,
// unit: 'percent',
// }
cs.get('background-position').y;
// CSSMathSum {
// operator: 'sum',
// values: CSSNumericArray {
// 0: CSSUnitValue { value: -10, unit: 'px' },
// 1: CSSUnitValue { value: 100, unit: 'percent' },
// },
// }
.content {
background-position: center bottom 10px;
}
sample.css
sample.js
computedStyleMap
CSS.registerProperty({
name: "--gradientStart",
syntax: "<color>",
initialValue: "#61BFD9",
inherits: true
});
@property --gradientStart {
syntax: "<color>";
initialValue: "#61BFD9";
inherits: true;
};
Proposed CSS syntax
Current JS syntax
Polyfill: PostCSS Register Property
"Houdini" is a reference to CSS being able to free itself
from the constraints of big bang releases
Part 2:
.app__header {
background-image: linear-gradient(#e66465, #9198e5);
}
Having individual access to the variables means
we can update the gradient dynamically!
:root {
--gradientStart: #e66465;
--gradientEnd: #9198e5;
}
.app__header {
background-image: linear-gradient(
var(--gradientStart),
var(--gradientEnd)
);
}
Codepen: Magic Gradient
document.addEventListener('mousemove', (e) => {
// Translate mouseX into degrees for HSL
const xDegree = getXDegree(e.clientX) // 0 - 360
// Update custom properties
setStyle('--grad-start', xDegree) // => 180
setStyle('--grad-end', xDegree + 120) // => 300
})
Magic Gradient: Animated Gradient
<html style="--grad-start: 180; --grad-end: 300">
:root {
--grad-start: 180;
--grad-end: 300;
}
body {
background: linear-gradient(
hsl(var(--grad-start), 100%, 75%),
hsl(var(--grad-end), 100%, 50%)
)
}
CSS
JS
HTML
Codepen: Magic Gradient
p {
&::before {
content: 'hsl(' var(--grad-start) ', 100%, 75%)';
}
&::after {
content: 'hsl(' var(--grad-end) ', 100%, 50%)';
}
}
Magic Gradient: Pseudo-content & Custom Props
Didn't work 😭
🤯
p {
// Map custom-idents hueStart & hueEnd to Custom Props
counter-reset:
hueStart var(--grad-start)
hueEnd var(--grad-end);
// Use the value returned by counter(custom-ident)
&::before {
content: 'hsl(' counter(hueStart) ', 100%, 75%)';
}
&::after {
content: 'hsl(' counter(hueEnd) ', 100%, 50%)';
}
}
Magic Gradient: Pseudo-content & Custom Props
Type-juggling in CSS… 🤪
Technique: stacked divs, animated opacity & z-index
<header class="app__header">
<!-- Backgrounds -->
<div class="jeff1-bg"></div>
<div class="jeff2-bg"></div>
<div class="jeff3-bg"></div>
<!-- Portraits -->
<figure class="header__jeffs">
<img src="/img/jeff1.jpg" alt="Jeff YEAH" />
<img src="/img/jeff2.jpg" alt="Jeff again" />
<img src="/img/jeff3.jpg" alt="It's all Jeff" />
</figure>
</header>
telling machines what to do, letting them figure out to how to do it
<header class="app__header">
<!-- Backgrounds -->
<div class="jeff1-bg"></div>
<div class="jeff2-bg"></div>
<div class="jeff3-bg"></div>
<!-- Portraits -->
<figure class="header__jeffs">
<img src="/img/jeff1.jpg" alt="Jeff YEAH" />
<img src="/img/jeff2.jpg" alt="Jeff again" />
<img src="/img/jeff3.jpg" alt="It's all Jeff" />
</figure>
</header>
Current structure
<header class="app__header">
<figure class="header__jeffs">
<img
src="/img/jeff1.jpg"
alt="Jeff YEAH"
data-grad-start="#61bfd9"
data-grad-end="#0551b4"
/>
<img
src="/img/jeff2.jpg"
alt="Jeff again"
data-grad-start="#15AF88"
data-grad-end="#6C429A"
/>
<img
src="/img/jeff3.jpg"
alt="It's all Jeff"
data-grad-start="#EEA031"
data-grad-end="#930560"
/>
</figure>
</header>
Desired structure: declarative
<img src="jeff1.jpg" data-grad-start="#61bfd9" data-grad-end="#0551b4" />
<img src="jeff2.jpg" data-grad-start="#15AF88" data-grad-end="#6C429A" />
<img src="jeff3.jpg" data-grad-start="#EEA031" data-grad-end="#930560" />
This won't work
😭
:root {
--gradientStart: #61BFD9;
--gradientEnd: #0551B4;
}
@keyframes interpolate {
from {
--gradientStart: #61BFD9;
--gradientEnd: #0551B4;
}
to {
--gradientStart: #15AF88;
--gradientEnd: #6C429A;
}
}
.app__header {
animation: interpolate 3s ease-in-out;
}
The browser knows how to turn one number into another
But if the variable type isn't a number…
¯\_(ツ)_/¯
Part 3:
CSS.registerProperty lets us fix the type of the Custom Property
Properties & Variables API
CSS.registerProperty({
name: "--gradientStart",
syntax: "<color>",
initialValue: "#61BFD9",
inherits: true
});
CSS.registerProperty({
name: "--gradientEnd",
syntax: "<color>",
initialValue: "#0551b4",
inherits: true
});
const jeffs = [...hero.querySelectorAll("img")];
const jeffNum = jeffs.length;
export const showJeff = (currentIndex = 0) => {
// Extract gradient start/end from data attribute
const jeff = jeffs[currentIndex];
const { gradientStart, gradientEnd } = jeff.dataset;
// Update the Custom Props
root.style.setProperty("--gradientStart", gradientStart);
root.style.setProperty("--gradientEnd", gradientEnd);
// Slide next <img/> in (and get the next pair of gradients)
jeff.style.zIndex = 2;
jeff
.animate([
{ opacity: 0, transform: "scale(1.2, 1.2)" },
{ opacity: 1, transform: "scale(1, 1)" }
], {duration: 3000})
.onfinish = () => {
const nextIdx = currentIndex + 1;
setTimeout(switchJeff, 3000, jeff, nextIdx % jeffNum);
};
};
Updating --gradientStart/End triggers a smooth transition
.app__header {
transition: --gradientStart 3s, --gradientEnd 3s;
background-image: linear-gradient(
var(--gradientStart),
var(--gradientEnd)
);
}
CSS.layoutWorklet.addModule("/js/worklets/masonry.js");
.main__gallery {
--padding: 20;
--columns: 5;
display: layout(masonry);
}
main.js
main.css
display:
familiar property, new value
[emotions not to scale]
Part 3:
visit: ishoudinireadyyet.com
Download: Microsoft Edge Insider
Part 4:
Explainer: Houdini Spellbook
Aggregator: Awesome-CSS-Houdini
Examples: css-houdini.rocks
examples: Google Chrome Labs
in-depth articles: developers.google.com
Part 5:
Shareable, reusable, drop-in code: extra.css
<header class="app__header">
<figure class="header__jeffs">
<img
src="/img/jeff1.jpg"
alt="Jeff YEAH"
data-grad-start="#61bfd9"
data-grad-end="#0551b4"
/>
<img
src="/img/jeff2.jpg"
alt="Jeff again"
data-grad-start="#15AF88"
data-grad-end="#6C429A"
/>
<img
src="/img/jeff3.jpg"
alt="It's all Jeff"
data-grad-start="#EEA031"
data-grad-end="#930560"
/>
</figure>
</header>
2014-2019
What Houdini really means
@oliverturner
Bonus slide: have an easter egg!