CSS Layout API (Houdini)
The CSS Layout API is part of the CSS Houdini project. It allows developers to define their own custom layout algorithms using JavaScript. This means you can create new display modes (like display: layout(masonry)) that the browser's layout engine executes.
1. The Concept
Traditionally, CSS layout is limited to what the browser provides (block, flex, grid, table). If you wanted a layout that didn't fit these models (like a masonry layout or a circular layout), you had to use JavaScript to calculate positions and set absolute positioning on elements. This is often slow and janky because it runs on the main thread and fights with the browser's rendering pipeline.
The CSS Layout API runs in a Worklet, allowing the browser to optimize the layout calculations and run them efficiently during the layout phase of the rendering pipeline.
2. Defining a Layout Worklet
You create a separate JavaScript file (e.g., masonry.js) and define a class with a layout() generator function.
// masonry.js
class MasonryLayout {
static get inputProperties() {
return ['--padding', '--columns'];
}
async intrinsicSizes(children, edges, styleMap) {
// Optional: Calculate min/max content sizes
}
async layout(children, edges, constraints, styleMap, breakToken) {
// children: The child elements to layout
// edges: The box edges (padding, border, scrollbar)
// constraints: The available space
// styleMap: The styles of the parent element
const inlineSize = constraints.fixedInlineSize;
const padding = parseInt(styleMap.get('--padding').toString());
const columnValue = styleMap.get('--columns').toString();
const columns = parseInt(columnValue);
const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns;
const childFragments = [];
const columnOffsets = Array(columns).fill(0);
for (let child of children) {
// Perform layout for the child
const fragment = await child.layoutNextFragment({
fixedInlineSize: childInlineSize
});
// Find the shortest column
const minOffset = Math.min(...columnOffsets);
const columnIndex = columnOffsets.indexOf(minOffset);
// Position the child
fragment.inlineOffset = padding + (columnIndex * (childInlineSize + padding));
fragment.blockOffset = padding + minOffset;
// Update column height
columnOffsets[columnIndex] += fragment.blockSize + padding;
childFragments.push(fragment);
}
const autoBlockSize = Math.max(...columnOffsets) + padding;
return {
autoBlockSize,
childFragments,
};
}
}
registerLayout('masonry', MasonryLayout);
3. Registering the Worklet
In your main JavaScript file:
if ('layoutWorklet' in CSS) {
CSS.layoutWorklet.addModule('masonry.js');
}
4. Using it in CSS
.container {
display: layout(masonry);
--padding: 10;
--columns: 3;
}
5. Key Differences from Main Thread JS
- Worklet Scope: It runs in a separate scope. No access to DOM,
window, ordocument. - Performance: Designed to be highly performant.
- Integration: It integrates directly into the browser's layout pass.
programming/javascript/vanilla/javascript programming/javascript/vanilla/css-painting-api programming/javascript/vanilla/css-typed-om programming/javascript/vanilla/web-workers