Joan León PRO
⚡️ Web Performance Consultant | Speaker | Staff Frontend Engineer at @AdevintaSpain | @GoogleDevExpert in #WebPerf | @cloudinary Ambassador
.CSS { }
.CSS { }
.CSS-Houdini { }
The objective of the CSS-TAG Houdini Task Force (CSS Houdini) is to jointly develop features that explain the “magic” of Styling and Layout on the web. - CSS Houdini
.CSS-Houdini { }
Paint API
Typed OM API
Properties & Values API
Animation Worklet
Layout API
Worklets
Font Metrics API
Box Tree API
Parser API
.CSS-Houdini { }
chrome://flags/#enableexperimental-web-platform-features
.Extendiendo-CSS { }
Worklets
.Extendiendo-CSS { }
.Extendiendo-CSS .Worklets { }
Un worklet es básicamente lo que vamos a utilizar para conectar con el motor de CSS del navegador.
Se trata de un módulo en Javascript donde definiremos la "magia" que podremos usar en CSS.
No tenemos control sobre la ejecución de los worklets, el motor de renderizado es quien hace las llamadas cuando es necesario.
No tiene impacto en el rendimiento, porque cada worklet funciona dentro de su propio hilo de ejecución.
.Extendiendo-CSS .Worklets { }
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('PlaceholderBoxPainter.js');
}
class PlaceholderBoxPainter {
paint(ctx, size) {
// Magic 🎩
}
}
registerPaint('placeholder-box', PlaceholderBoxPainter);
worklet
js
.Extendiendo-CSS .Worklets { }
(async () => {
if (typeof CSS === 'undefined' || !('paintWorklet' in CSS)) {
document.querySelector('.no-dice').hidden = false;
return;
}
const paintModule = URL.createObjectURL(new Blob(
[ document.querySelector('[type="houdini/paint-worklet"]').textContent],
{ type: "text/javascript" }
));
await CSS.paintWorklet.addModule(paintModule);
})();
js
.Extendiendo-CSS .Worklets { }
Paint API
.Extendiendo-CSS { }
.Extendiendo-CSS .Paint-API{ }
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('PlaceholderBoxPainter.js');
}
@supports (background: paint(slanted-bg)) {
.el span {
background: paint(slanted-bg);
}
}
css
js
class SlantedBg {
constructor () {
this.hue = 110;
}
paint (ctx, geom, properties, args) {
// draw random colors
ctx.fillStyle = 'hsl(' + this.hue + ', 100%, 50%)';
this.hue += 10;
...
}
}
registerPaint('slanted-bg', SlantedBg)
worklet
registerPaint('slanted-bg', class {
constructor () {
this.hue = 110;
}
paint (ctx, geom, properties, args) {
// draw random colors
ctx.fillStyle = 'hsl(' + this.hue + ', 100%, 50%)';
this.hue += 10;
...
}
})
.Extendiendo-CSS .Paint-API{ }
.Extendiendo-CSS .Paint-API{ }
.Extendiendo-CSS .Paint-API{ }
Properties & Values API
.Extendiendo-CSS { }
.Extendiendo-CSS .Properties-Values-API { }
CSS.registerProperty({
name: '--line-with',
syntax: '<number>',
inherits: false,
initialValue: 1
});
js
'<length>' Valores de longitud
'<length> | <percentage>' Ambas combinaciones, pero no calc ()
'<length-percentage>' Ambas combinaciones + calc () de ambos tipos
'big | bigger | BIGGER' Acepta cualquier valor
'<length>+' Acepta una lista de valores de longitud
'<length>'
'<number>'
'<percentage>'
'<length-percentage>'
'<color>'
'<image>'
'<url>'
'<integer>'
'<angle>'
'<time>'
'<resolution>'
'<transform-function>'
'<custom-ident>'
.Extendiendo-CSS .Properties-Values-API { }
.Extendiendo-CSS .Properties-Values-API { }
.Extendiendo-CSS .Properties-Values-API { }
.Extendiendo-CSS .Properties-Values-API { }
.Extendiendo-CSS .Properties-Values-API { }
@property --theme {
syntax: '<color>+';
initial-value: #fff;
inherits: true;
}
if ("registerProperty" in CSS) {
CSS.registerProperty({
name: "--theme",
syntax: "<color>+",
initialValue: "#fff",
inherits: true
});
}
.Extendiendo-CSS .Properties-Values-API { }
input
output
@property --highlight-color {
inherits: true;
initial-value: red;
syntax: "<color>";
}
@property --gap-spacing {
inherits: false;
initial-value: 1em;
syntax: "<length-percentage>";
}
[
{
"name": "--highlight-color",
"inherits": true,
"initialValue": "red",
"syntax": "<color>"
},
{
"name": "--gap-spacing",
"initialValue": "1em",
"syntax": "<length-percentage>"
}
]
import properties from './styles.css.properties.json';
if (window.CSS && CSS.registerProperty) {
for (const descriptor of properties) {
CSS.registerProperty(descriptor);
}
}
.Extendiendo-CSS .Properties-Values-API { }
input
output
js
Typed OM API
.Extendiendo-CSS { }
.Extendiendo-CSS .Typed-OM-API { }
La Type OM API es una extensión del modelo de objetos CSS existente (CSSOM) que expone los valores CSS como objetos JavaScript tipados, en lugar de una simple cadena de texto como lo son hoy.
Con Type OM API, los valores CSS son ahora miembos de una nueva clase base CSSStyleValue. Existen varias subclases de las CSSStyleValue que describen con más precisión un tipo de valor CSS.
CSSUnitValue
CSSKeywordValue
CSSPositionValue
CSSImageValue
CSSTransformValue
CSSMathValue
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Strings!
CSSOM
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Typed OM
.Extendiendo-CSS .Typed-OM-API { }
new CSSMathSum(CSS.vw(100), CSS.px(-10)).toString();
// "calc(100vw + -10px)"
new CSSMathNegate(CSS.px(42)).toString() // "calc(-42px)"
new CSSMathInvert(CSS.s(10)).toString() // "calc(1 / 10s)"
new CSSMathProduct(CSS.deg(90), CSS.number(Math.PI/180)).toString();
// "calc(90deg * 0.0174533)"
new CSSMathMin(CSS.percent(80), CSS.px(12)).toString();
// "min(80%, 12px)"
new CSSMathMax(CSS.percent(80), CSS.px(12)).toString();
// "max(80%, 12px)"
Valores matemáticos
.Extendiendo-CSS .Typed-OM-API { }
calc(1px - 2 * 3em)
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
Expresiones anidadas
.Extendiendo-CSS .Typed-OM-API { }
el.attributeStyleMap.set('font-size', CSS.em(2));
el.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }
el.attributeStyleMap.set('opacity', CSS.number(.5));
el.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' }
attributeStyleMap
.Extendiendo-CSS .Typed-OM-API { }
.foo {
background-position: center bottom 10px;
transform: translateX(1em) rotate(50deg) skewX(10deg);
vertical-align: baseline;
width: calc(100% - 3em);
}
css
.Extendiendo-CSS .Typed-OM-API { }
const cs = document.querySelector('.foo').computedStyleMap();
cs.get('vertical-align');
// CSSKeywordValue {
// value: 'baseline',
// }
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' },
// },
// }
cs.get('width');
// CSSMathSum {
// operator: 'sum',
// length: 2,
// values: CSSNumericArray {
// 0: CSSUnitValue { value: -90, unit: 'px' },
// 1: CSSUnitValue { value: 100, unit: 'percent' },
// },
// }
cs.get('transform');
// CSSTransformValue {
// is2d: true,
// length: 3,
// 0: CSSTranslate {
// is2d: true,
// x: CSSUnitValue { value: 20, unit: 'px' },
// y: CSSUnitValue { value: 0, unit: 'px' },
// z: CSSUnitValue { value: 0, unit: 'px' },
// },
// 1: CSSRotate {
// is2d: true,
// angle: CSSUnitValue { value: 50, unit: 'deg' },
// x: CSSUnitValue { value: 0, unit: 'number' },
// y: CSSUnitValue { value: 0, unit: 'number' },
// z: CSSUnitValue { value: 1, unit: 'number' },
// },
// 2: CSSSkewX {
// is2d: true,
// ax: CSSUnitValue { value: 10, unit: 'deg' },
// },
// }
computedStyleMap
.Extendiendo-CSS .Typed-OM-API { }
Animation Worklet
.Extendiendo-CSS { }
.Extendiendo-CSS Animation-Worlet { }
Layout API
.Extendiendo-CSS { }
.Extendiendo-CSS .Layout-API {}
.Conclusiones { }
Polylills
Esto no es para mí
Houdini NO es como Babel para CSS
Libre albedrío
🤔
Visualizo una charla...
.Recursos { }
Joan León
#Javascript
#CSS
#PostCSS
#Animation
#SVG
By Joan León
JSDay Canarias 2018: CSS siempre se ha considerado la parte menos controlable, complicada, mágica y en ocasiones algo aleatoria. Houdini nos ofrece un conjunto de APIs y herramientas Javascript que nos dan la posibilidad de extender CSS para acceder al proceso de diseño y estilo del motor de renderizado del navegador.