这篇文章发布于 2020年09月13日,星期日,23:13,归类于 CSS相关。 阅读 26874 次, 今日 10 次 10 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=9572
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、基本概念和一些属性API
CSS Layout API
可以让开发者自定义布局方式,例如实现瀑布流布局效果,全新的表格布局效果等。
CSS Layout API
的使用分为两部分。
第1部分是在CSS中设置自定义的布局名称,和Flex布局、Grid布局一样,也是使用display
属性,区别在于CSS Layout API
自定义的布局使用layout()
函数表示。
例如自定义一个瀑布流布局名为'masonry'
,则在CSS中使用的语法就会是下面这样:
.container { display: layout(masonry); }
第2部分是使用JavaScript书写关于如何布局的功能模块。CSS Layout API已经约定好了布局模块书写的语法,如果单看规范文档,会觉得像天书一样,无从下手,实际上,自定义布局的代码书写是有固定的套路的,也就是大体的JavaScript代码框架都是固定的,只需要在指定的函数位置书写对容器元素以及容器子元素的位置和尺寸进行设置的代码就可以实现想要的效果了。
举个例子,要自定义一个瀑布流布局效果,JavaScript部分的代码该如何书写呢?
首先在页面中需要调用关于瀑布流布局的模块,可以使用layoutWorklet
,具体代码如下所示:
if ('layoutWorklet' in CSS) {
// 把自定义的瀑布流布局脚本添加到Layout Worklet中
CSS.layoutWorklet.addModule('masonry.js');
}
然后就是重点也是难点所在,masonry.js的代码该如何书写?其实基本结构非常简单,代码如下所示:
registerLayout('masonry', class {
async layout(children, edges, constraints, styleMap, breakToken) {
// 这里写瀑布流布局相关代码
}
});
从上面的例子可以看出,CSS Layout API
大的框架并不难理解,上手成本几乎没有。但是要想玩转CSS Layout API
,我可以断言,大部分的前端开发者够呛,因为其中关于定位和布局的API是需要对CSS的尺寸体系有一定程度的了解才知道是什么意思的。
所以我张鑫旭
觉得有必要整理下CSS Layout API
中出现的属性名称及其对应的含义(假设在默认的文档流方向下),详见下表。
属性名 | 对应含义 |
---|---|
inlineSize | 内联方向的尺寸,对应于width 属性。 |
blockSize | 块级方向的尺寸,对应于height 属性。 |
inlineOffset | 相对于容器元素在内联方向的偏移,默认是左侧偏移的大小。 |
blockOffset | 块级方向的偏移,默认是顶部偏移的大小。 |
minContentSize | 最小内容尺寸。 |
maxContentSize | 最大内容尺寸。 |
availableInlineSize | 可用内联方向的尺寸,通常表示可用宽度。 |
availableBlockSize | 可用块级方向的尺寸,通常表示可用高度。 |
fixedInlineSize | 固定的内联方向的尺寸,水平方向的宽度通常是可以确定的,因此即使没有设置width 属性也是有值。 |
fixedBlockSize | 固定的块级方向的尺寸,如果height 设置的是auto ,属性值会是null 。 |
percentageInlineSize | 内联方向的百分比尺寸。 |
percentageBlockSize | 块级方向的百分比尺寸。 |
inlineStart | 水平起始方向content box外边缘到border box外边缘的距离,在默认文档流下就是padding-left 和border-width-left 外加滚动条宽度(如果有)的计算值之和。 |
inlineEnd | 水平结束方向content box外边缘到border box外边缘的距离。 |
blockStart | 垂直起始方向content box外边缘到border box外边缘的距离。 |
blockEnd | 垂直结束方向content box外边缘到border box外边缘的距离。 |
inline | 整个水平方向content box外边缘到border box外边缘的距离之和。 |
block | 整个垂直方向content box外边缘到border box外边缘的距离之和。 |
还记不记得在展示CSS自定义布局模块基本结构代码那里出现了下面这部分JavaScript代码:
layout(children, edges, constraints, styleMap, breakToken)
这个layout()
方法是整个CSS Layout API
的核心所在,其中出现了5个参数,理解了上面这5个参数值,CSS Layout API
也就理解了80%。
其中,前3个参数与表1中的这些属性密切相关,后1个参数styleMap
用户获取外部设置的样式,主要是用来获取外部设置的CSS自定义属性值,最后1个参数breakToken
用在打印等常见,大家可以暂时不用关心。
所以,接下来的重点会介绍children
、edges
、constraints
和styleMap
这4个参数。
二、layout()参数值之间的逻辑关系
//zxx: 如果你看到这段文字,说明你现在访问是体验糟糕的垃圾盗版网站,你可以访问原文获得很好的体验:https://www.zhangxinxu.com/wordpress/?p=9572(作者张鑫旭)
1. 参数children
参数children
表示设置了display:layout(zhangxinxu)
元素的子元素们,包含了子元素布局相关的一些信息。
我们可以通过循环获取所有子元素的布局信息,例如:
children.forEach(child => {
// child干嘛干嘛
});
上面代码中出现的变量child
包含1个属性和2个方法,可以用户获取子元素的尺寸等信息,具体如下:
- styleMap
- child.intrinsicSizes()
- child.layoutNextFragment(constraints, breakToken)
其中:
- child.styleMap
- 可以用来获取子元素的样式信息,具体参见接下来会介绍的“参数styleMap”。
- child.intrinsicSizes()
-
此方法返回的是一个Promise对象,称为IntrinsicSizes对象,支持下面这2个属性:
- IntrinsicSizes.minContentSize(只读)
- IntrinsicSizes.maxContentSize(只读)
也就是
child.intrinsicSizes()
返回的是最小内容尺寸和最大内容尺寸的大小。 - child.layoutNextFragment(constraints, breakToken)
- 此方法返回的也是一个Promise对象,称为LayoutFragment对象,其中包括下面2个只读属性和2个可写属性:
- LayoutFragment.inlineSize(只读)
- LayoutFragment.blockSize(只读)
- LayoutFragment.inlineOffset(可写)
- LayoutFragment.blockOffset(可写)
在CSS Layout API的实际应用中,
LayoutFragment.inlineOffset
和LayoutFragment.blockOffset
是2个高频使用的属性,因为可以对子元素的偏移位置进行设置,实现想要的布局定位效果。child.layoutNextFragment()
方法中的constraints
参数就是指的接下来要介绍的layout()
方法中constraints
参数。
2. 参数edges
edges
称为LayoutEdges对象,所有的属性都是只读的,用来返回容器元素内容边缘到边框边缘的距离大小,支持下面这些属性:
- LayoutEdges.inlineStart(只读)
- LayoutEdges.inlineEnd(只读)
- LayoutEdges.blockStart(只读)
- LayoutEdges.blockEnd(只读)
- LayoutEdges.inline(只读)
- LayoutEdges.block(只读)
各个属性的含义参见表1,当使用LayoutFragment.inlineOffset
和LayoutFragment.blockOffset
对子元素进行定位的时候往往需要用到LayoutEdges对象,以确保定位的精确,因为LayoutFragment.inlineOffset
和LayoutFragment.blockOffset
定位是相对于border box边缘的。
3. 参数constraints
constraints
称为LayoutConstraints对象,所有的属性都是只读的,用来返回布局容器的尺寸信息,这些属性包括下面这些:
- LayoutConstraints.availableInlineSize(只读)
- LayoutConstraints.availableBlockSize(只读)
- LayoutConstraints.fixedInlineSize(只读)
- LayoutConstraints.fixedBlockSize(只读)
- LayoutConstraints.percentageInlineSize(只读)
- LayoutConstraints.percentageBlockSize(只读)
各个属性的含义参见表1,其中需要注意的是上面这些属性浏览器并非全部都支持,目前Chrome仅支持fixedInlineSize
和fixedBlockSize
这两个属性,其他属性暂时还不能使用,以后可能会支持。
4. 参数styleMap
styleMap
称为StylePropertyMapReadOnly对象,是一个不包含set()
和clear()
等写入方法的类Map结构的数据类型,主要用来获取容器元素或者子元素的常规CSS属性值或者CSS自定义属性值,多使用get()
方法单个获取,例如有如下CSS代码:
.container { --gap: 10; display: layout(someLayout); }
此时使用styleMap.get('--gap')
就获得--gap
的属性值。
需要注意的是,styleMap
获得的CSS属性需要提前指定好,通过静态属性inputProperties
完成,例如:
registerLayout('someLayout', class { // 指定可以输入的CSS属性 static inputProperties = ['line-height']; async layout(children, edges, constraints, styleMap, breakToken) { // 可以得到容器元素的行高计算值大小 let lineHeightParse = styleMap.get('line-height'); } });
三、文本居中同时一侧对齐的布局案例
上面的参数和属性的介绍属于基础理论知识,可以让我们了解CSS Layout API
的精神内核,接下来就是通过具体的案例让大家知道具体该如何使用。
由于瀑布流布局的案例过于复杂,因此,这里我会举一个更简单也更实用的案例演示下如何使用CSS Layout API
自定义一种全新的布局效果。
已知有一串数字列表,相关HTML如下所示:
<section align="right"> <p>102.00</p> <p>23.80</p> <p>12,334.00</p> <p>2.88</p> <p>99.99</p> </section>
为了方便一看看出数字的大小,这个列表显然需要右对齐,但是由于列表宽度较宽,简单的右对齐可能会有大量留白不好看,此时产品经理就希望这列数字个体右对齐的同时整体居中对齐,效果如下图所示。
在过去只能通过在列表元素的外面再包裹一层元素的方法实现,现在有了CSS Layout API
我们就可以自已创造一种对齐布局方式,比方说我们定义这种全新的布局名称是center
,于是就可以对列表容器元素进行如下所示的设置:
section { display: layout(center); }
新建一个名为layout-center.js用来书写布局代码,然后在页面中引入该文件模块:
CSS.layoutWorklet.addModule('layout-center.js');
layout-center.js的代码如下所示:
registerLayout('center', class { // 需要获取相应值的CSS属性 static inputProperties = ['line-height', 'text-align']; // 需要,不能省略 async intrinsicSizes(children, edges, styleMap) {} // 主布局方法 async layout(children, edges, constraints, styleMap, breakToken) { // 外部CSS属性值获取,主要是行高和对齐方式 let lineHeight = styleMap.get('line-height').value; let textAlign = styleMap.get('text-align').value; // 返回所有子元素的内容长度数据 const childrenSizes = await Promise.all(children.map((child) => { return child.intrinsicSizes(); })); // 求得最大内容宽度,对齐需要 const maxContentSize = childrenSizes.reduce((max, childSizes) => { return Math.max(max, childSizes.maxContentSize); }, 0) + edges.inline; // 下面这4个const语句是固定且必要的 const availableInlineSize = constraints.fixedInlineSize - edges.inline; const availableBlockSize = constraints.fixedBlockSize ? constraints.fixedBlockSize - edges.block : lineHeight; const childConstraints = { availableInlineSize, availableBlockSize }; const childFragments = await Promise.all(children.map((child) => { return child.layoutNextFragment(childConstraints); })); // 垂直偏移的起始距离 let blockOffset = edges.blockStart; // 设置每一个子元素的垂直偏移大小 childFragments.forEach((fragment, index) => { // 设置当前子元素的水平偏移大小 fragment.inlineOffset = Math.max(0, availableInlineSize - maxContentSize) / 2; // 右对齐需要增加最大内容尺寸的偏差值 if (textAlign == 'right' || textAlign == 'end') { fragment.inlineOffset += (maxContentSize - childrenSizes[index].maxContentSize); } // 设置当前子元素的垂直偏移大小 fragment.blockOffset = blockOffset; // 偏移递增 blockOffset += lineHeight; }); // 最终容器元素的高度大小 const autoBlockSize = blockOffset + edges.blockEnd; return { autoBlockSize, childFragments, }; } });
各个语句的含义均在上面代码中使用注释描述了,相信不难理解。
自定义模块布局代码中有时候会使用一个名为layoutOptions
的静态对象,可以指定子元素的显示类型和基本尺寸表现(例如类似于块级元素):
registerLayout('center', class { static inputProperties = ['line-height', 'text-align']; static layoutOptions = { childDisplay: 'normal', sizing: 'block-like' }; ... });
上面的例子有可访问的demo演示页面,您可以狠狠地点击这里:CSS Layout API实现列表居中右对齐demo
CSS Layout API
目前在Blink内核浏览器下才有效果,例如Chrome浏览器或者改用Blink内核的Microsoft Edge浏览器,如果你的浏览器版本比较新,但是却看不到效果,请尝试使用Canary金丝雀版本,或者在地址栏输入chrome://flags然后开启Experimental Web Platform features这一项进行体验。
上面的演示页面还演示了居中同时左对齐的效果,具体如下图所示。
CSS Layout API
还有不少其他细节知识,例如元素设置了display:layout(zhangxinxu)
之后,其原本的尺寸就会崩塌,不习惯的开发者可能会比较茫然,不知道发生了什么事情,需要在JavaScript代码中以autoBlockSize
属性形式把元素的高度进行返回。
又例如由于布局效果需要在JavaScript模块引入之后执行,因此在页面加载的过程中会看到页面内容跳动的糟糕体验问题出现,还需要通过一些技术手段规避(上面的实例页面是通过margin
负值实现的)。
另外,实际开发的时候,CSS Layout API
更多的是与CSS自定义属性配合使用,这样会表现出更加灵活的动态特性,例如如果我们使用CSS Layout API
实现瀑布流布局效果,那么各个元素之间的间隙,每一栏的宽度等就需要CSS自定义属性进行设置。这样,当需要调整间隙或者宽度的时候,只需要修改对应的CSS自定义属性值就可以了,CSS变量的语义和优势就发挥出来了。
四、最后的点评
最后,对CSS Layout API进行点评下,CSS Layout API是非常强大的特性,可以让Web布局有更多的想象空间,由于现在浏览器还没有完全开放给用户,因此暂时实用性还不足。同时由于学习成本比较高,需要CSS和JavaScript同时有一定的造诣才能驾驭,因此,CSS Layout API
以后注定是小部分开发者的玩具,最终出现的局面一定是少部分人创造,大部分人直接使用。
好了,本文内容就这样,感谢阅读,欢迎分享。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=9572
(本篇完)
- CSS届的绘图板CSS Paint API简介 (0.245)
- 折腾:瀑布流布局(基于多栏列表流体布局实现) (0.242)
- 剪映APP的视频特效如何在Web中JS实现 (0.242)
- CSS direction属性简介与实际应用 (0.161)
- 一万年了,CSS text-align-last终于可以用了 (0.161)
- JS检测CSS属性浏览器是否支持的多种方法 (0.124)
- 我使用CSS模拟个假的数字loading效果 (0.121)
- 狠狠地研究了下 PerformanceObserver API (0.121)
- Nice! Safari也支持CSS @property规则了 (0.121)
- 我是如何通过CSS向JS传参的 (0.069)
- CSS inherit是继承,那initial和unset呢? (RANDOM - 0.003)
学习了 ~
“最后1个参数breakToken用在打印等常见”
应该是个病句
好久没更新了
有点像在手写 canvas的排版
提高了前端对于底层API的控制能力,但是同时对图形 和几何学有了更高的要求
想到当年IE的expression…
有时容器或者body没有设置line-height为具体数值会失效,加个容错处理
let lineHeight = styleMap.get(‘line-height’).value||1.8;
上手有点高
这个太厉害了
兼容性可以简单提一下吗?chrome 80.0.3987.162 好像不兼容,demo没有效果。
可以升级下。