Web技巧 (04)
在Web技巧第三期中,我们了解到了排版中断字连接符、原生的延迟加载、不规则阴影处理、Sass颜色函数和CSS自定义属性的结合以及CSS中对齐等知识点。在这一期中,我们将围绕着CSS中的布局来展开。在这一期中将会向大家介绍CSS Shapes给布局带来的变化(Web艺术)、在Flexbox和Grid布局中如何控制最后一行的剩余项以及如何使用Flexbox和Grid实现响应式布局、CSS怎么实现瀑布流布局、CSS怎么构建对角容器和两个还未得到支持的CSS特性:subgrid
和带两个属性值的display
。
Web艺术: CSS Shapes给布局带来的变化
@Andy Clarke在使用CSS给新的设计带来很多边界性的突破。在他的新文章《Art Direction For The Web Using CSS Shapes》中,超越了基本的CSS Shapes,并向大家展示了如何使用它们给Web布局带来的突破。
CSS Shapes相关的文章在互联网上有很多,特别是去年,@Rachel Anddrew写了一篇教程,得新介绍了CSS Shapes。这篇文章向大家介绍了使用CSS Shapes的基本知识。如果你想了解CSS Shapes中的shape-outside
、shape-margin
、shape-image-threshold
等属性如何使用,那么这篇文章是篇不错的教程。虽然介绍CSS Shapes的文章很多,甚至使用CSS Shapes创建的示例也很多,可是很多都没有超出CSS的基本形状,即circle()
、ellipse()
、inset()
,即使使用polygon()
的例子也很少超出一些局限性。而CSS Shapes形状所能提供的创造力是非常强大的,如果一直还是用一些基本形状的示例来阐述CSS Shapes魅力的话,难免会令人感到失望。庆幸的是,@Andy Clarke的文章让你能重新感到CSS Shapes魅力所在。因为他以自己的灵感和想象力,为我们展示了使用CSS Shapes五种不同类型的布局(V字型布局、Z型布局、曲线布局、对角线形状布局和旋转布局),而这些布局效果更独特,更吸引人。
我们来看一个V字型的布局:
<main>
<div class="placeholder"></div>
<h1>V-Shapes</h1>
<p>After giving the ... </p>
<!-- 其他的HTML结构 -->
</main>
其中div.placeholder
是用来当作CSS Shapes的占位符,比如们要的一个V字型,其实是两个三角形拼出来的:
如此一来,可以借助::before
和::after
来使用两个图片,不过对于类似这样的三角形,还可以借助polygon()
来绘制。为了方便,可以基于像**Clippy**这样的在线工具来完成:
.shape-placeholder {
width: 100%;
&::before,
&::after {
content: '';
width: 35vw;
height: 100vh;
}
&::before {
float: left;
shape-outside: polygon(0 0, 0% 100%, 70% 100%);
}
&::after {
float: right;
shape-outside: polygon(100% 0, 30% 100%, 100% 100%);
}
}
得到的效果如下:
利用类似的原理,还可以实现很多其他的效果。
当然,很多时候一些形状是我们需要借助其他工具来帮助我们完成的。这样的工具在Chrome和Firefox浏览器都提供了相应的插件(**CSS Shapes Editor **),这些工具具体的使用可以阅读下面的文章:
相比而言,Firefox下的工具更好用一些。假设你想让文本围绕下图来做布局,你可以先用图片当作背景图,成为CSS Shapes的一个占位图,比如下面这张图:
接着先随便绘制几个点,使用polygon(55px -1px, 9.57% 8.45%)
,再借助浏览器插件跟着背景图来描边(拖动点,添加点):
然将最终的polygon()
的值复制出来给shape-outside
:
shape-outside: polygon(55px -1px, 9.57% 8.45%, 15.06% 17.15%, 0.41% 20.5%, -6.92% 29.07%, -22.67% 35.16%, -50.89% 41.37%, -75.8% 43.73%, -79.44% 49.93%, -58.94% 52.06%, -47.95% 47.33%, -3.26% 39.75%, -9.85% 47.7%, -39.52% 51.3%, -64.43% 58.76%, -73.59% 77.77%, -94.47% 84.85%, -93.73% 90.56%, -53.07% 84.1%, -40.26% 67.45%, 0.77% 67.58%, -8.42% 80.59%, -21.22% 90.42%, -20.84% 99.38%, -4.57% 97.33%, 13.13% 101.34%, 33.12% 94.38%, 43.75% 74.89%, 53.42% 62.5%, 67.94% 37.95%, 78.99% 24.94%, 101.86% 17.67%, 96.55% 11.86%, 74.31% 6.78%, 57.63% 1.97%)
最终你能看到的效果如下:
如此一来,你可以发挥你的想象力和创造力,做出任何你觉得很有意思的东西。
在第五届CSS Conf会议上 @勾股 和 @张鑫旭 两位大大分享的主题中都提到CSS Shapes相关的特性。
Flexbox 和 Grid 布局中如何控制剩余的项
今天和同事一起探讨了Flexbox和Grid布局中剩余项的控制。这里将相关过程和涉及到的东西在这里聊聊。比如像下图这样的一个效果:
在Flexbox布局中我们很好的处理最后一行单一的项目(Flex项目),而在Grid布局中最后一行单一的项目(Grid项目)就无法自动跨列:
左侧红色为Flexbox布局,右侧绿色为Grid布局。
就上述而言,最后一行出现单一项目有点类似于印刷领域。在印刷领域,段落末尾不占一行的单词被称为**寡妇**。
在分页媒体查询和多列布局中的这些排版行为可以使用CSS的属性
window
和orphans
来处理。
回到我们的示列当中来,如果我们在Grid布局中,也要能像Flexbox布局一样,很好的处理最后一项。那么这个时候我们就需要借助CSS伪类选择器来处理。即结合:last-child
和:nth-child
。通过这两个伪类选择器我们可以检测一个项目是不是寡妇(独立的一个),并相应的调整其样式。对于该技术,@Heydon Pickering在其博客中有过相关的介绍,而且将该技术称为 数量查询(Quantity Queries) ,也有人将该技术称为取模。
利用数量查询特性,我们可以对最后一个网格项做相应的处理:
.item:last-child:nth-child(3n - 2) {
grid-column: span 3;
}
另外一个场景,Flex项目设置了一个max-width
,而且容器设置了justify-content
的值为center
、space-around
、space-between
和space-evenly
时,最后一行的效果如下图所示。
而我们实际想要的效果如Grid布局一样:
对于这样的效果,在Grid中很轻易就能完成:
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
grid-gap: 10px;
}
但在Flexbox中就没有那么容易了。要实现同等的效果,需要借助额外的手段,即在Flexbox容器中添加空的Flex项目。这个空标签的数量我们可以下面的JavaScript来帮助我们自动插入:
function checkCount(current_total, next){
if(current_total % 12){ // 12 is smallest number that 3 and 4 go in to
current_total += 1; // Proper way to do ++
checkCount(current_total, next); // Recursion!
} else {
next(current_total);
}
}
function addDummyElementsToCards(container_id){
var container = document.getElementById(container_id);
var card_count = container.children.length;
checkCount(card_count, function(final_count){
var dummy_element;
for(i = 0; i < card_count; i++){
dummy_element = document.createElement('div');
dummy_element.className = 'dummy_card';
container.appendChild(dummy_element);
}
});
}
addDummyElementsToCards('flexbox');
同时给.dummy_card
添加样式:
.dummy_card {
height: 0;
flex: 1 1 310px;
}
其中310px
是flex-basis
,你可以根据自己的需要来设置。
另外还有一个有关于Flexbox更全面的示例:
更为复杂的是我们并不知道容器的宽度是多少,也不清楚每行显示多少个项目。针对于这样的场景,我们使用CSS Grid中的auto-fill
或auto-fit
关键词来替代repeat()
函数中的具体数值。这样我们就不需要考虑一行到底是显示多少个项。同时配合minmax()
函数来设置一个最小宽度,比如310px
,再设置一个最大宽度1fr
。这样就可以更好的实现响应式的卡片布局:
有关于这方面的介绍还可以阅读:
CSS实现瀑布流布局方案
在《纯CSS实现瀑布流布局》一文中,我们介绍了在CSS中如何实现瀑布流布局(不借助任何JavaScript来实现):
有关于CSS实现瀑布流布局的相关教程还可以阅读:
@tobiasahlin在他的新教程中《CSS masonry with flexbox, :nth-child()
, and order
》介绍了Flexbox布局中如何使用伪类选择器:nth-child()
和order
实现的瀑布流布局:
具体代码不在这里展示了,感兴趣的同学可以阅读@tobiasahlin写的教程!
除此之外,@Emmanuel Olaojo写了一个很轻的JavaScript库,用少许的JavaScript代码配合CSS Grid布局实现的一个瀑布流布局,这个效果被称为Magic Grid。
更厉害的是,随着CSS Houdini成熟之后,我们可以借助CSS Houdini的Layout Worklet相关API封装一个瀑布流的组件,然后在实际使用的时候,我们只需要像下面这样即可:
<div id="masonry">
<div class="item"></div>
<!-- 省略N个div.item -->
<div class="item"></div>
</div>
#masonry {
display: layout(masonry);
--padding: 20;
--columns: 3;
}
详细的可以点击这里进行了解。
有关于CSS Houdini中Layut API的介绍可以点击这里进行了解。
如果你是位React的爱好者,还可以@RyLee Harrison写的一个示例,该示例是React和CSS Houdini结合起来创建的瀑布流布局。当然,你要是不喜欢React,而是喜欢Vue,不仿尝试着使用Vue和CSS Houdini结合起来,也创建一个瀑布流的布局。期待您的分享。
CSS实现对角容器
对角容器(Diagonal Containers)是什么呢?上个图,来说明一下这里所说的对象容器:
@Sebastiano Guerriero把这个效果封装成了一个UI组件(Diagonal Hero Component),它是Hero Component组件的扩展。
实现这个效果的方法有很多种,比如:
- @Erik Kennedy 的Creating Non-Rectangular Headers
- @Mary Lour 的Slopy Elements with CSS3
- @Jeremy 的Angled Edges with CSS Masks and Transforms
- @kilianvalkhof 的Sloped edges with consistent angle in CSS
而@Sebastiano Guerriero的《Diagonal Containers in CSS》教程中向大家介绍了如何使用CSS的clip-path
实现这个效果。
.hero--diagonal {
position: relative;
background-color: transparent;
&::before {
content: '';
position: absolute;
top: -50px;
left: 0;
width: 100%;
height: calc(100% + 100px);
clip-path: polygon(0% 0%, 100% 50px, 100% 100%, 0% calc(100% - 50px));
}
.hero__content {
position: relative;
z-index: 1;
}
}
添加一点修饰样式,看到的效果如下:
如果.hero--diagonal::before
中使用了背景图片,还需要添加下面的样式:
.hero--diagonal::before {
// ...
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
CSS Subgrid
@rachelandrew 在Newcastle上分享了一个主题:Grids All The Way Down:
@rachelandrew在该主题中花了大部的篇幅来解释 CSS Grid Layout Module Level 2 规范中的 subgrid
。对于subgrid
相关的讨论,@rachelandrew已经有过多次的讨论了,比如在2018年7月份的时候就在Smashing Magazine上就有聊过CSS Grid Level 2中的subgrid
。
虽然目前还没有任何浏览器支持subgrid
特性,但不用过于担心,我相信这一天很快就会到来的。 不过你要是想了解一些有关于subgrid
相关的特性,可以看看@rachelandrew给我们提供的截图。而且@rachelandrew也将会不定期的将有关于subgrid
相关的Demo放到 Grid by Example 中。
Grid by Example 是一个专门介绍 CSS Grid的网站,该网站涵盖有关于CSS Grid的示例、教程、视频和资源等。
用于列和行的subgrid
.d{
gap: 10px;
grid-column: 2 / 5;
grid-row: 2 / 4;
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}
在本例中,subgrid
位于父网格的列和行上。这意味着在subgrid
的网格区域中没有隐式网格,因为两个维度都绑定到父网格上的可用轨道上。
仅用于列的subgrid
.d{
gap: 10px;
grid-column: 2 / 5;
grid-row: 2 / 4;
display: grid;
grid-template-columns: subgrid;
}
在这个示例中,只对列使用了subgrid
。这意味着我们可以使用隐式网格轨道来添加尽可能多的行。行使用它们自己的大小,就像嵌套网格不使用subgrid
一样。
仅用于行的subgrid
.d{
gap: 10px;
grid-column: 2 / 5;
grid-row: 2 / 4;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: subgrid;
}
在本例中,将行设置为subgrid
,并将列定义为1fr
。正如你所看到的,列轨道不与父轨道对齐,因为它们使用自己的网格定义。
根据subgrid
网格线来设置子网格项目的位置
一旦在grid-template-columns
或grid-template-rows
中设置了值为subgrid
,就表示你创建了一个subgrid
(子网格),那么他就会像grid
一样,有自己的网格线(显式的或隐式的)。在子网格中其网格线也是从1
开始,如上图所示。如果我们要在子网格中设置子网格项目的位置,可以根据子网格线编号(而不是父网格线编号)来设置。
父网格的网线名会传递到子网格中
如果我们在父网格中指定了网格线名称,那么这些网格线的名称会被传递到子网格中,并添加在子网格中定义的任何名称。
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr [col-start] 1fr 1fr 1fr [col-end] 1fr 1fr 1fr;
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
grid-column: 2 / 7;
grid-row: 2 / 4;
display: grid;
grid-template-columns: subgrid;
grid-template-row: subgrid;
}
.subitem {
grid-column: col-start / col-end;
grid-row: 1 / 3;
}
显式给子网格设置网线名
在子网格中我们也可以像父网格一样,给子网格添加网线名称:
比如上图,在父网格中有两条列网格线分别叫col-start
和col-end
,同时我们在相同的网格线位置处为子网格重新设置网格线的名称为col-start sub-c
和 col-end sub-f
。
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr [col-start] 1fr 1fr 1fr [col-end] 1fr 1fr 1fr;
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
grid-column: 2 / 7;
grid-row: 2 / 4;
display: grid;
grid-template-columns: subgrid [sub-a] [sub-b] [sub-c] [sub-d] [sub-e] [sub-f];
grid-template-row: subgrid;
}
.subitem {
grid-column: sub-c / sub-f;
grid-row: 1 / 3;
}
子网格会继承父网格的gap
值
在子网格中会继承父网格的gap
值。当然你也可以在子网格中改变gap
值。
subgrid
除了上述具有的特性之外,还有其他的一些特性,比如 子网格中网格项目的大小可以更改父网格轨道的大小。如果你想了解subgrid
更多的特性或相关变化,可以点击下面这些链接进行了解:
- Slides and resources from Frontend NE
- CSS Grid Level 2: Here Comes Subgrid
- Firefox: implement grid layout for subgrid
- Chrome: implement subgrid
- Grid Level 2 specification
你所不知道的display
属性
块元素和内联元素是学习CSS最早接触的概念之一。其中display
属性可以让我改变元素的盒子模式。但一直以来,我们所知道的display
只是用来改变元素盒子模式用于布局,却很少有深入的去了解该属性。@Rachel Andrew在她的新博文《Digging Into The Display Property: The Two Values Of Display》中从另一个角度向大家阐述了display
属性。
该文没有介绍怎么去讨论display
属性各个值如何使用,而是集中讨论display
的flex
或grid
值。因为取值flex
或grid
时定义了一些有趣的东西,而只有了解或理解了这些东西才能更好地,更轻松的使用CSS来进行布局。
在 CSS Display Module Level 3 中对早期的dsplay
的取值方式有所更改。对于我们这些从事CSS工作多年的人来说,乍一看这似乎有些不寻常,但我认为这些变化确实有助于解释当我们更改元素上的display
值时发生了什么。
其实在我看到这篇文章的时候,我也非常的诧异,display
还能使用两个值。这是什么鬼,后来阅读了一下规范才有所了解。对于CSS中的几个概念,块元素、行内元素、文档流和正常聊这里就不做相应的阐述,不了解这几个概念的同学最好还是花点时间去了解一下。这里着重提一下display
的 内部(Inner)和外部(Outer)值。
在**CSS Display Module Level 3** 中,display
的值定义为两个关键词。这些关键词定义display
的外部值,它将是inline
或block
,将定义元素在布局中与其他元素一起的行为。它们还定义了元素的内部值,用于指定该元素的直接子元素的行为方式。
这意味着,我们以前使用的 display: grid
将会变成display: block grid
。即,你需要的是一个块级的网格容器,一个包含所有块属性的元素,可以给这个元素指定width
、height
、margin
和 padding
,它会拉伸来填充容器。然而,该容器的子元素已经赋予了grid
的内部值,因此成为网格项目。网格项目的行为会根据网格规范中的指定的display
行为,告诉浏览器我们想要的布局是什么。这种思考方式是非常有用的,它能够直接解释我们如何使用各种布局方法。比如,你使用了display: inline flex
,表示的是容器是一个内联元素框,其中包含的子元素是Flex项目。
回过头来,这样我们就会碰到display
取一个值和两个值的状态,那么他们的取值之间有没有什么相应的关系呢?可以看看下表:
单个值 | 两个值 | 描述 |
---|---|---|
block |
block flow |
正常流内的块盒子 |
flow-root |
block flow-root |
定义一个BFC的块盒子 |
inline |
inline flow |
正常流内的内联盒子 |
inline-block |
inline flow-root |
定义一个BFC的内联盒子 |
list-item |
block flow list-item |
正常文档流和带有附加标记的块盒子 |
flex |
block flex |
带有内部伸缩布局的块盒子 |
inline-flex |
inline flex |
带有内部伸缩布局的内联盒子 |
grid |
block grid |
带有内部网格布局的块盒子 |
inline-grid |
inline grid |
带有内部网格布局的内联盒子 |
table |
block table |
带有内部表格布局的块盒子 |
inline-table |
inline table |
带有内部表格布局的内联盒子 |
上表就是display
属性取一个值(老的用法)和两个值(新的用法)之间的映射关系,所以你也不用担心这会破坏我们以前的使用。我们来看一个示例:
.container {
display: block grid;
}
上面的代码等同于:
.container {
display: grid;
}
如果我们在.container
中使用的是inline grid
:
.container {
display: inline grid;
}
那么它就等同于:
.container {
display: inline-block;
}
因此在使用的时候,可以根据上表来对照。
也就是说,当你在CSS中定义一个框(盒子,即元素)的布局时,可以根据它与布局中其他框之间的关系来定义这个框的行为。同时还定义了该框子元素的行为。在显式地将这些值声明为两个独立的东西之前,你可以以这种方式考虑,这将帮助你理解更改display
的值会发生什么?
有关于这方面更详细的介绍,可以阅读@Rachel Andrew的《Digging Into The Display Property: The Two Values Of Display》教程。
小结
在这一期中将会向大家介绍CSS Shapes给布局带来的变化(Web艺术)、在Flexbox和Grid布局中如何控制最后一行的剩余项以及如何使用Flexbox和Grid实现响应式布局、CSS怎么实现瀑布流布局、CSS怎么构建对角容器和两个还未得到支持的CSS特性:subgrid
和带两个属性值的display
。Nike Huaraches Are Hyped