blog
blog copied to clipboard
CSS Layout API
简介
CSS Layout API 能让网页开发人员写自己的布局算法。它是 CSS Houdini #23 的一部分
这样,我们在 CSS 里写display
属性值的时候,就不局限于浏览器已经支持的那几种布局了,诸如display: block
、display: flex
等,我们可以写display: layout(myLayout)
Layout API 里的所有内容都在 Logical Coordinate System(逻辑坐标系统)里计算。这样的话,网站的书写模式就可以自动影响到你写的布局了。
对于熟悉文本从左到右书写的开发人员来说,Logical Coordinate System 到我们日常用到的名词的对应关系如下:
Logical | 在CSS里写的 |
---|---|
inlineSize | width |
inlineStart | left |
inlineEnd | right |
blockSize | height |
blockStart | top |
blockEnd | bottom |
知道这个名词对应关系有什么意义呢?等会在编写 Layout Worklet 的时候,你会看到代码里大量用到了第一列的属性值。
参考 https://github.com/w3c/css-houdini-drafts/blob/master/css-layout-api/EXPLAINER.md
接下来,我们用一个例子来理解下 Layout API。
例子
它最终的运行效果是:
图1. 宽度自适应(若无图,请戳链接)
图2.1 接受参数,eg.间距(若无图,请戳链接)
图2.2 接受参数,eg.列数(若无图,请戳链接)
完整代码见 src/css-layout-api/demo1.masonry
关键代码如下,建议配合着代码注释看,方便理解。
在 index.html 里
<style>
ul {
--padding: 5;
--columns: 3;
display: layout(masonry);
}
</style>
<!-- 内含 10 个高度不等的 li -->
<ul>...</ul>
<script>
if ('layoutWorklet' in CSS) {
// 把 module script 添加到 Layout Worklet 里
CSS.layoutWorklet.addModule('my_layout_masonry.js');
}
</script>
在 my_layout_masonry.js 里
registerLayout('masonry', class {
static get inputProperties() {
return [ '--padding', '--columns' ];
}
*intrinsicSizes() { /* TODO */ }
/**
* 渲染引擎,在浏览器的layou 阶段时的回调
* @param children 要执行layout元素的子元素列表
* @param edges 在 logical coordinate system 里的 borders, scrollbar 和 padding 的大小
* @param constraints 生成的片段应该满足的条件,该对象里提前计算了当前layout的一些属性。
* eg. inline-size (width), block-size (height)
* @param styleMap 当前layout的只读style
*/
*layout(children, edges, constraints, styleMap) {
// 1. 确定当前layout的内部大小, width
const inlineSize = constraints.fixedInlineSize;
const padding = parseInt(styleMap.get('--padding').toString());
const columnValue = styleMap.get('--columns').toString();
let columns = parseInt(columnValue);
if (columnValue == 'auto' || !columns) {
columns = Math.ceil(inlineSize / 350); // 默认每个宽350px
}
const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns;
const childFragments = yield children.map((child) => {
// 2. 对子节点进行布局,根据 columns
return child.layoutNextFragment({fixedInlineSize: childInlineSize});
});
// 3. 算出'auto'块的大小。就能知道子元素的最大 height
let autoBlockSize = 0;
const columnOffsets = Array(columns).fill(0);
for (let childFragment of childFragments) {
const min = columnOffsets.reduce((acc, val, idx) => {
if (!acc || val < acc.val) {
return {idx, val};
}
return acc;
}, {val: +Infinity, idx: -1});
// 设置相对于父元素的 offset(除这两之外其它的属性都是只读的)
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);
}
// 4. 返回 fragment
return {autoBlockSize, childFragments};
}
});
参考 https://github.com/GoogleChromeLabs/houdini-samples