Vincent De Oliveira · @iamvdo
Hello, it’s @iamvdo
For CSS ?
Build CSS Polyfills
Today, it’s complicated impossible to polyfill CSS, even with JS.
https://philipwalton.com/articles/the-dark-side-of-polyfilling-css/
Build its own graphical effects
Build its own langage addons
in a nutshell
Innovate
Welcome Houdini!
* More or less (depending browser)
Houdini is
JS-in-CSS
Or rather CSS-by-JS
Can I use Houdini?
Progressive enhancement FTW
Warning, fresh paint!
Evrything that is showed here can stop running anytime ¯\_(ツ)_/¯
CSSOM++
// CSSOM
el.style.width = '50px'
el.style.setProperty('width', '50px')
el.style.setProperty('transform', 'translate(' + x + 'px, ' + y + 'px)')
// Typed OM
el.attributeStyleMap.set('width', '50px')
el.attributeStyleMap.set('width', CSS.px(50))
el.attributeStyleMap.set('transform', new CSSTranslate(CSS.px(x), CSS.px(y)))
el.computedStyleMap().get('width') // CSSUnitValue {value: 50, unit: 'px'}
let [x, y] = [10, 10];
let transform = new CSSTranslate(CSS.px(x), CSS.px(y))
transform.x.value = 50
transform.toString() // "translate(50px, 10px)"
// Parse CSS
let css = CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
let css = CSSStyleValue.parse('transform', 'translate3d(10px,10px,0) scale(0.5)');
/*
CSSTranslateValue {
0: CSSTranslate {
is2D: false
x: CSSUnitValue {value: 10, unit: 'px'}
y: CSSUnitValue {value: 10, unit: 'px'}
z: CSSUnitValue {value: 10, unit: 'px'}
}
1: CSSScale {
is2D: true
x: CSSUnitValue {value: 0.5, unit: 'number'}
y: CSSUnitValue {value: 0.5, unit: 'number'}
z: CSSUnitValue {value: 1, unit: 'number'}
}
is2D: false
}
*/
Typed OM == Houdini foundation!
Not specific to Houdini
.el {
box-shadow: 0 3px 3px rgba(0,0,0,.75);
}
.el:hover {
box-shadow: 0 15px 10px rgba(0,0,0,.75);
}
/* Extend CSS: custom properties */
.el {
box-shadow: var(--box-shadow-x, 0) var(--box-shadow-y, 3px)
var(--box-shadow-blur, 3px)
var(--box-shadow-color, rgba(0,0,0,.75));
}
.el:hover {
--box-shadow-y: 15px;
--box-shadow-blur: 10px;
}
.el {
text-shadow:
var(--x, 0)
var(--y, 3px)
var(--blur, 3px)
var(--color, rgba(0,0,0,.75));
}
// Change in JS
el.addEventListener('mousemove', e => {
el.attributeStyleMap.set('--x', e.offsetX)
el.attributeStyleMap.set('--y', e.offsetY)
el.attributeStyleMap.set('--blur', blur)
})
Properties & Values API
// Set an animatable property
CSS.registerProperty({
name: '--box-shadow-blur',
syntax: '<length>',
inherits: false,
initialValue: '0px'
})
.el {
transition-property: --box-shadow-blur, --box-shadow-y;
transition-duration: .45s;
}
.el:hover {
--box-shadow-y: 15px;
--box-shadow-blur: 10px;
}
Creative Animate a gradient
Worklet
CSS.paintWorklet.addModule('paint.js')
registerPaint()
paint()
registerPaint('circle', class {
paint(ctx, geom, props, args) {
// Get the center point and radius
const x = geom.width / 2;
const y = geom.height / 2;
const radius = Math.min(x, y);
// Draw the circle
ctx.fillStyle = 'deeppink';
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
}
}
.el {
background-image: paint(circle);
}
registerPaint('circle-props', class {
static get inputProperties() { return ['--circle-color']; }
paint(ctx, geom, props, args) {
// Determine the center point and radius.
const x = geom.width / 2;
const y = geom.height / 2;
const radius = Math.min(x, y);
// Draw the circle
ctx.fillStyle = props.get('--circle-color').value;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
}
}
.el {
--circle-color: deepskyblue;
background-image: paint(circle-props);
}
registerPaint('circle-ripple', class { static get inputProperties() { return [ '--circle-color', '--circle-radius', '--circle-x', '--circle-y' ]} paint(ctx, geom, props, args) { const x = props.get('--circle-x').value; const y = props.get('--circle-y').value; const radius = props.get('--circle-radius').value; } }
el.addEventListener('click', e => { el.classList.add('animating'); el.attributeStyleMap.set('--circle-x', e.offsetX); el.attributeStyleMap.set('--circle-y', e.offsetY); });
.el {
--circle-radius: 0;
--circle-color: deepskyblue;
background-image: paint(circle-ripple);
}
.el.animating {
transition: --circle-radius 1s,
--circle-color 1s;
--circle-radius: 300;
--circle-color: transparent;
}
Creative Artistic drawing
Creative Backgrounds Corners gradient
Polyfill Backgrounds corner-shape
Polyfill Creative Backgrounds background-filter / opacity / rotate
Creative Backgrounds Highlighter marker
Creative Borders Tooltip arrow
Creative Borders Rough borders
Reusable and trashable worklet ?
Slightly bypassed using seed random numbers
Creative Masks Random bubbles
Creative Masks Irregular grid
Creative 🤯 JS-in-CSS
Worklet
CSS.layoutWorklet.addModule('layout.js')
registerLayout()
layout()
registerLayout('center', class {
*layout(children, edges, constraintSpace, props) {
let childFragments = [];
for(let child of children) {
let childFragment = yield child.layoutNextFragment();
let childHalfSize = childFragment.inlineSize / 2;
let parentHalfSize = constraintSpace.fixedInlineSize / 2;
childFragment.inlineOffset = parentHalfSize - childHalfSize;
childFragments.push(childFragment);
}
return { childFragments };
}
});
.parent {
display: layout(center);
}
registerLayout('position', class {
static get inputProperties() { return ['--position-x']; }
*layout(children, edges, constraintSpace, props) {
let posx = props.get('--position-x').value;
let pos = constraintSpace.fixedInlineSize / (100 / posx);
}
});
.parent {
display: layout(position);
--position-x: 50%;
}
registerLayout('position', class { static get inputProperties() { } *layout(children, edges, constraintSpace, props) { } });
parent.addEventListener('click', e => { parent.classList.add('animating'); let pos = 100 * e.offsetX / parentWidth; parent.attributeStyleMap.set('--position-x', CSS.percent(pos)); });
.parent {
display: layout(position);
--position-x: 50%;
}
.parent.animating {
transition: --position-x 1s;
}
Creative Masonry
Polyfill Android RelativeLayout
Creative SVG Path
Worklet
CSS.animationWorklet.addModule('anim.js')
registerAnimator()
new WorkletAnimation()
Based on a Scott Kellum idea
registerAnimator('simple', class { animate(currentTime, effect) { effect.localTime = currentTime; } });
.cube { --angle: 0; transform: rotateX(var(--angle)) rotateZ(45deg) rotateY(-45deg); }
new WorkletAnimation('simple',
new KeyframeEffect(el, [
{ '--angle': 0 },
{ '--angle': '1turn' }
],
{ duration: 1 }
),
new ScrollTimeline({
scrollSource: scrollElement,
timeRange: 1
}),
).play();
registerAnimator('parallax', class {
constructor(options = {}) {
this.factor = options.factor || 1;
}
animate(currentTime, effect) {
effect.localTime = currentTime * this.factor;
}
});
for (let i = 0; i < els.length; i++) {
new WorkletAnimation('parallax',
new KeyframeEffect(els[i], [
{ transform: new CSSTranslate(0, 0) },
{ transform: new CSSTranslate(0, CSS.px(scrollHeight)) }
], { duration: 1 }
),
new ScrollTimeline({
scrollSource: scrollElement,
timeRange: 1
}),
{ factor: (i / 2) * (1.2 - 1) + 1 }
).play();
}
animation-timeline: scroll()
CSS Parser, Render Tree, Font Metrics
Sky’s the limit
Wait & See
element()
, backdrop-filter
, CSS Shaders 😍, etc.paint()
not on links