前端开发者学堂 - fedev.cn

这些用于布局的新属性能用吗?

发布于 大漠

这个话题是@Rachel Andrew于2019年9月在英国爱丁堡举办的Finch front的一场演讲,其原标题是Does it work? Using the new CSS Layout,我在这里将其译为这些用于布局的新属性能用吗?。这个话题中的内容非常的有意思,其中有些内容在小站上也有过相应的介绍。今天看了该PPT,觉得有些内容还是非常的有意思,值得和大家分享。

分享的主题

该主题的PPT比较长,总共有111页面,涉及到有关于布局多个新的知识点,如果你不想花时间阅读后面的内容,可以点击下图直接阅读PPT:

在过去,我们所接触到的Web布局都是一些最基本的布局,时至今日,Web布局系统变得更为复杂,但了变得更为灵活,而且这些汇总到一起可以称得上是一套布局的系统,该系统变得复杂而又灵活。简单地说,她将以GridFlexbox系统为主线,并由一系列CSS的概念和避属性来进行扩展,让Web布局更为灵活和强大。而这些概念也有利于我们更好的理解Web上怎么来更好的实现所需要的布局。

@Rachel Andrew这次的主题分享从整体的角度来看CSS的布局。通过这个主题的学习,我们可以了解到现代的Web布局和传统的布局有何不同,又有哪些CSS新特性来支持现在或将来的Web布局。在接下来的内容中,我们将会了解到CSS的GridFlexbox多列布局BFC书写模式和关于滚动的一些特性。如果你对这方面感兴趣的话,欢迎继续往下阅读。

CSS是独特的也是令人惊奇的

社区总是有一种声音,特别是今天有众多优秀的JavaScript框架出现的时候,这种声音在社区中更为强烈:

CSS很简单,没有任何技术含量

听到这种声音,我总是喜欢用下图来反驳:

看到上图,估计很多人又要说我矫情了。事实上呢?只有自己心理才明白。

回到CSS的世界中来,CSS在很多开发人员中是太微不足道了,也从不担心CSS有什么难度,但是很多开发人员又不理解CSS,甚至觉得要理解CSS太难了,有的时候甚至会让自己抓狂。

这也正是CSS独特和令人感到惊奇之处。

这里推荐几篇文章给大家:

上面这几篇文章有助于你更好的阅读W3C的规范,更利于你提高自己CSS方面的能力。

提高CSS水平最好的方式是掌握如何阅读W3C规范

普通流

普通流实际上就是大家平时所说的文档流(实际上在规范中没有文档流这个词,只不过是大家习惯性用文档流这个词而以)。普通流是CSS中最为基本的概念,也是重要的概念之一。而在Web的布局世界中,普通流又被称为无布局的布局

默认情况下,HTML中的元素都会按照其在HTML源码文档中的先后位置从左至右自上而下依次堆叠:

不过上面这种说法仅仅是以前的一种说法,对于现代的Web来说,HTML中的元素不仅仅是从左至右自上而下依次堆叠,也有从右至左自上而下依次堆叠,比如“人民网阿文版”,它就是一种从右向左自上而下堆叠

而源的顺序(普通流)又是非常重要的,比如用户在使用tab键来操作你的页面时,会显得尤其突出:

另外一点,一个具有好的源的顺序的文档,更有利于用户阅读你的文档,如果你想检验你的文档流是否写得好,可以通过不添加任何(仅运用客户端默认的样式规则)样式来查阅你的Web:

不管是在HTML中使用dir属性,还是在CSS中使用书写模式(writing-mode)属性都可以让你较为轻易的处理普通流的顺序。除此之外,在CSS Grid和Flexbox布局中,还可以有其他的姿势来改变源的顺序,比如在Flexbox中给Flex项目显式设置order属性,比如在Grid中使用grid-columngrid-rowgrid-area等属性显式指定Grid项目位置等。在后面我们会多少聊到这方面的知识点。

Grid或Flexbox

是选择Grid还是Flexbox,这是一个可怕的问题。

我们用如何实现列布局来举例。比如我们要实现类似下图这样列布局的效果:

早年的Web布局方式中,可能首先会考虑到的是CSS浮动(float。只不过使用浮动布局,需要每个列的宽度。如果每列的宽度用的都是百分比来设置列的宽度时,当浏览器窗口缩小到一定的程度(没有足够宽度容纳列的内容时)很可能会出现列的换行。

除此之外,使用浮动布局还有一些其他的问题,最起码你总要考虑在有浮动的地方使用清除浮动。如果从CSS的最初设计原则来说,浮动也不是用来处理Web布局的,而是用来实现文本围绕图片(元素)的排版效果。只不过在特定的历史环境之下,使用他来实现我们想要的Web布局效果。

随着CSS的新特性的出现,Web的布局不在局限于其中的一两种方案。同样拿列的布局来举例,实现列的布局有很多种方式。咱们先来看多列布局。

多列布局

在CSS中有专门为多布局的属性,那就是CSS Multi-column Layout Module Level 1模块中提供的column-*columnsbreak-*等属性。我们可以使用这些属性轻易的实Web多列布局。比如使用:

  • column-count控制列数
  • column-width控制列宽
  • column-gap控制列间距
  • column-span实现跨列
  • break-inside控制列是否被打断

我们来看一个使用这些属性实现的多列布局:

.columns {
    column-count: 3;
    column-width: 25vw;
}

section {
    break-inside: avoid;
}

.columns h1,
    section h2 {
    column-span: all;
}

实际是多列布局的属性在不同的浏览器得到的效果不一样:

在多列布局当中,默认情况下,列的宽度是灵活的,会根据容器自动计算,比如上面的示例中,如果把column-width样式禁用掉,改变容器宽度时,列宽度也会自动调整,效果如下:

有关于多列布局更多的介绍可以阅读下面相关文章:

不容忽视的display

display属性我想大家都熟悉,如果用一句话来描述的话:

CSS的display属性可以用来改变元素的视觉格式化模型,即改变框的类型

要彻底理解display属性将需要了解CSS的基本概念,比如视觉盒模型、盒模型等。建议大家花点时间阅读下面这几篇文章:

任何一个Web页面都会由很多种不同的HTML元素组成,而任何一个HTML元素都是一个(即盒子)。这些框主要有两种类型,块框内联框

而且在默认情况之下,每个元素都会有自身的框模型,简单地说,元素的display有着对应的属性值。浏览器解析任何一个文档流时,默认情况之下会按下图方式来渲染文档流:

如果回到Web的布局系统中,时至今日最为主要的是用于Grid和Flexbox上的。如果用视觉格式化模型来描述的话:

  • 元素上显式声明display的值为flexinline-flex,即创建了Flexbox格式化上下文(Flexbox Formatting Context),该元素就会生成一个Flex容器,同时它的子元素就会变成Flex项目
  • 元素上显式声明display的值为gridinline-grid,即创建Grid格式化上下文(Grid Formatting Context),该元素就会生成一个Grid容器,同时它的子元素就会变成Grid项目

接下来同样用多列为例子。假设我们有三个列:

<div class="wrapper">
    <div class="column">One</div>
    <div class="column">Two</div>
    <div class="column">Three</div>
</div>

现不显式更改divdisplay属性值的情况下,你可能看到的效果如下:

如果在容器上显式设置display的值为flexinline-flexdiv.column则不会是自上往下堆栈排列,而会自左向右排列,效果如下:

除了使用flexinline-flex之外,也可以在容器上显式设置display的值为gridinline-grid

如果你对display有过了解的话,就会知道display生成的框具备两个基本特性:

  • 内部显示类型,它定义(如果它是一个不可替换的元素)它生成的格式化上下文的类型,指定其后代框的布局方式
  • 外部显示类型,它指示主体框本身如何参与流布局

display规范Level 3中,该属性的值定义为两个关键字。这些关键字定义了display的外部值,它将是inlineblock的,因此定义元素在布局中与其他元素一起的行为。它们还定义了元素内部值(该元素的直接子元素的行为方式)。

这意味着,当你使用display: grid时,实际上是使用了displah: block grid。意思是说,布局需要的是一块级网格容器。也就是说该元素具有块元素的属性,即可以给元素设置widthheightpaddingmargin,而且元素自身也会自动拉伸来填充容器。同时该元素的子元素已被赋予了grid的内部值,因此子元素就成了大家所说的网格项目。

这种思考方式能更好的帮助我们解释Web中的各种布局方法。比如你设置了display: inline flex,意思是需要一个内联元素框,而其子元素是Flex项目。

用张图来描述,更易理解一些:

更详细的介绍可以回过头看CSS盒模型中的块轴和内联轴一节

在布局中,内联轴和块联轴和书写模式有着紧密的关系,如下图所示:

因此也会直接影响布局的效果。

有关于这部分,稍后我们也会聊到。

到目前为止,大家习惯性的只是给display显式的设置一个值,在不久的将来可能更习惯性的设置两个值

Firefox 70开始支持两个属性值,接下来的示例效果都将能在Firefox 70以上得到较好的支持。在写这篇文章时使用的浏览器是Firefox Nightly 71。

特别是在布局的时候,显式的设置两个值,更有易于大家理解布局的方式:

单个值 两个值 描述
block block flow 正常流内的块级盒子
inline inline flow 正常流内的内联级盒子
inline-block inline flow-root 定义一个BFC的内联级盒子
list-item block flow list-item 正常文档流和带有附加标记的块级盒子
flow-root block flow-root 定义一个BFC的块级盒子
flex block flex 带有内部伸缩布局的块级盒子
inline-flex inline flex 带有内部伸缩布局的内联级盒子
grid block grid 带有内部网格布局的块级盒子
inline-grid inline grid 带有内部网格布局的内联级盒子
table block table 带有内部表格布局的块级盒子
inline-table inline table 带有内部表格布局的内联级盒子
none   从盒子树中移除,包括其所有后代元素
contents   元素替换为框树中的内容

回到我们的示例中来,尝试在将:

display: flex            ~> display: block flex
display: inline-flex     ~> display: inline flex
display: grid           ~>  display: block grid
display: inline-grid    ~> display: inline grid

有关于逻辑属性更多的介绍可以阅读:

创建新的BFC

众所周知,在使用浮动的时候,容易引起浮动元素父容器坍塌:

容器中的所有元素都浮动的话,容器元素就会塌陷;如果有任何一个非浮动元素存在,那么容器的高度将与非浮动元素高度等同

面对这种情景往往我们都使用BFC来处理这种事项。更形象的一点的示例我们可以使用BFC来防止文字环绕:

针对这一场景,用W3C标准来解释的话,大致是这样的:

在BFC中,每个盒子的左外边框紧挨着左边框的包含块(从右到左的格式化时,则为右边框紧挨)。即使在浮动里也是这样的(尽管一个盒子的边框会因为浮动而萎缩),除非这个盒子的内部创建了一个新的BFC(这种情况下,由于浮动,盒子本身将会变得更窄)。

以往我们在CSS中创建一个BFC,可以通过下面的方式:

  • 根元素或包含根元素的元素
  • 浮动元素(元素的 float 不是 none
  • 绝对定位元素(元素的 positionabsolutefixed
  • 行内块元素(元素的 displayinline-block
  • 表格单元格(元素的 displaytable-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 displaytable-caption,HTML表格标题默认为该值)
  • 匿名表格单元格元素(元素的 displaytabletable-rowtable-row-grouptable-header-grouptable-footer-group(分别是HTML tablerowtbodytheadtfoot的默认属性)或 inline-table
  • overflow 值不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layoutcontentstrict 的元素
  • 弹性元素(displayflexinline-flex元素的直接子元素)
  • 网格元素(displaygridinline-grid 元素的直接子元素)
  • 多列容器(元素的 column-countcolumn-width 不为 auto,包括 column-count1
  • column-spanall 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中

在新的display属性中,有一个新的值可以更易于创建BFC,即**display:flow-root**,如果用两个属的话则是block flow-root

如果你想深入的了解CSS中的BFC相关知识,可以阅读下面的文章:

不一样的inline-block

在CSS的display众多熟悉的属性值之中,我们最为熟悉的是blockinlineinline-block

从上面的表格上我们可以获知,在取值为两个值时,inline-block也将会与众不同。同样用实例来向给大家展示:

<!-- HTML -->
<div>
    <span class="inline">
        <img src="pxcat.jpg" alt="" />I am an inline thing
    </span>
    I follow the inline thing.
    <br />I am below the inline thing.
</div>

/* CSS without inline-block */
.inline {
    background-color: rgb(71, 73, 84);
    color: #fff;
    padding: 1em;
}

.inline img {
    float: left;
}

/* CSS with inline-block */
.inline {
    background-color: rgb(71, 73, 84);
    color: #fff;
    padding: 1em;
    display: inline-block;
}

.inline img {
    float: left;
}

效果如下:

同样的,我们也可以给display使用两个属性值:display: inline flow-root

简单的小结一下,在CSS中display取值 block flow-rootinline flow-root时都可以创建BFC。

令人难以琢磨的flex

在Flexbox中,flex属性是最令人难以琢磨的,也是最为复杂的一部分。该属性主要由flex-growflex-shrinkflex-basis三个属性组成。这三个属性可以控制一个Flex项目,主要表现在以下几个方面:

  • flex-grow:Flex项目的扩展比率,让Flex项目得到(伸张)多少Flex容器多余的空间(Positive free space)
  • flex-shrink:Flex项目收缩比率,让Flex项目减去Flex容器不足的空间(Negative free space)
  • flex-basis:Flex项目未扩展或收缩之前,它的大小是多少

之所说说他们复杂是因为Flex项目伸缩计算是一个复杂的过程。事实上他们计算是有一定的理论依据的,如果掌握他们之间的关系,就要显得容易得多:

大部分情形之下,我们都是使用flex属性来设置Flex项目的伸缩的值。其常见值的效果有:

  • flex: 0 autoflex:initial,这两个值与flex: 0 1 auto相同,也是初始值。会根据width属性决定Flex项目的尺寸。当Flex容器有剩余空间时,Flex项目无法扩展;当Flex容器有不足空间时,Flex项目收缩到其最小值min-content
  • flex: autoflex: 1 1 auto相同。Flex项目会根据width来决定大小,但是完全可以扩展Flex容器剩余的空间。如果所有Flex项目均为flex: autoflex:initialflex: none,则Flex项目尺寸决定后,Flex容器剩余空间会被平均分给是flex:auto的Flex项目。
  • flex: noneflex: 0 0 auto相同。Flex项目根据width决定大小,但是完全不可伸缩,其效果和initial类似,这种情况下,即使在Flex容器空间不够而溢出的情况之下,Flex项目也不会收缩。
  • flex: <positive-number>(正数)与flex: 1 0px相同。该值使Flex项目可伸缩,并将flex-basis值设置为0,导致Flex项目会根据设置的比例因子来计算Flex容器的剩余空间。如果所有Flex项目都使用该模式,则它们的尺寸会正比于指定的伸缩比。

在CSS中设置一个元素的基本大小可以通过width来设置,或者通过min-widthmax-width来设置元素的最小或最大宽度,在未来我们还可以通过contentmin-contentmax-contentfit-content等关键词来设置元素的大小。对于Flex项目,我们还可以通过flex-basis设置Flex项目大小。对于如何设置Flex项目的基本大小,我们可以围绕以下几点来进行思考:

  • flex-basis的值是auto?Flex项目显式的设置了宽度吗?如果设置了,Flex项目的大小将会基于设置的宽度
  • flex-basis的值是auto还是content?如果是auto,Flex项目的大小为原始大小
  • flex-basis的值是0的长度单位吗?如果是这样那这就是Flex项目的大小
  • flex-basis的值是0呢? 如果是这样,则Flex项目的大小不在Flex容器空间分配计算的考虑之内

用张图来概括,大致如下:

有关于flex更详细的介绍,还可以阅读:

Flexbox的对齐方式

在Flexbox中,如果不想不Flex容器的剩余空间扩展到Flex项目中的话,我们可以使用Flexbox中其他属性,比如justify-content来分配剩余空间。当然也可以给Flex项目设置margin值为处理Flex容器剩余空间。当然,在W3C规范中有一个独立的功能模块CSS Box Alignment Module Level 3来控制Flex项目在容器中的对齐方式。

@hj_chen(陈慧晶)老师在2018年就有做过方面的分享

Box Alignment Module Level 3除了可以应用在Flexbox上之外还常用于Grid布局上

我们来看一个实例:

实现上例的HTML结构大致如下:

<ul class="wrapper">
    <li>Item One <span>2</span></li>
    <li>Item Two <span>11</span></li>
    <li>Item Three <span>4</span></li>
    <li>Item Four <span>5</span></li>
</ul>

给其添加一些基本样式:

.wrapper {
    resize: horizontal;
    overflow: hidden;
    position: relative;
    margin: 0;
    padding: 0;
    border: 1px solid currentColor;
    border-radius: 0.5em;
    color: rgb(71, 73, 84);
    
    &::after {
        pointer-events: none;
        content: "";
        font-size: 14px;
        position: absolute;
        height: 24px;
        width: 24px;
        text-align: center;
        bottom: -10px;
        right: -2px;
        z-index: 2;
        background-color: transparent;
        color: #fff;
    }
    
    li {
        padding: 1em;
        background: #fff;
        display: flex;
        align-items: center;
    }
    
    li + li {
        border-top: 1px solid currentColor;
    }
    
    span {
        display: inline-flex;
        justify-content: center;
        align-items: center;
        background-color: rgb(71, 73, 84);
        color: #fff;
        font-weight: bold;
        font-size: 80%;
        border-radius: 100%;
        min-width: 1.5em;
        min-height: 1.5em;
        padding: 0.35em;
        line-height: 1;
    }
}

看到的效果如下:

如果我们要实现想要的效果,即让span和文本两端对齐,第一种方式是我们最常用的方式,即在Flex容器上使用justify-content:space-between;第二种方式,可以在span元素上使用margin-left: auto

.flex li {
    justify-content: space-between;
}

.margin li span {
    margin-left: auto;
}

最终效果如下:

安全和不安全对齐

当内容大于容器时,会造成内容的溢出。在这种情况下使用某些对齐模式可能会导致数据丢失。例如,如果侧边栏的内容居中,当它们溢出时,它们可能会将部分框移出可视区域(溢出),而且无法滚动到溢出的起始边缘。

要控制这种情况,可以显式指定溢出对齐模式。在溢出情况下,unsafe对齐遵循指定的对齐模式,这会导致数据丢失(上图右侧align-self: unsafe center;);,如果设置safe,可以避免数据丢失(如上图左侧:align-self: safe center;)。

这是Box Alignment规范中最近添加了一个概念使用safeunsafe关键词进行安全和不安全对齐的设置

针对上图,如果用代码来写的话大致如下:

// 上图右侧
.container {  
    display: flex;
    flex-direction: column;
    width: 100px;
    align-items: unsafe center;
}

// 上图左侧
.container {  
    display: flex;
    flex-direction: column;
    width: 100px;
    align-items: safe center;
}

有关于Box Alignment更多的介绍可以阅读:

书写模式

大家都知道在Web的世界中也有坐标系统,大家最为熟悉的就是x轴和y轴。如果不指定任何书写模式,x轴是水平的,从左到右。另一轴是y轴,从上到下。如果用xy轴来描述的话,更趋向于物理系统,但随着书写模式的到来,如果继续使用xy轴来描述会令大家更易于困惑。为此,在新的CSS世界中重新提出了两个概念,即:

  • 内联轴(Inline Axis):拿英文网站为例。阅读方向是从左到右。好比多个内联元素一样,从左向右依次排开,也类似于在块元素中使用display: inline。每一项都出现在同一行
  • 块轴(Block Axis):大家应该都知道,对于块元素不做任何操作的情况下,他的排列顺序是从上到下依次堆积在一起。好比对内联元素采用了display: block

内联轴和块轴与书写模式有着较大的关联,书写模式不同,内联轴和块轴会随之改变。如下图所示:

在该系统中,以前的物理属性(比如toprightbottomleftwidthheight)局限性显得越来越大。也因此在W3C有规范中提出了逻辑属性、内联轴和块轴等相关概念。特别是在CSS的书写模式引入到CSS的系统中之后,这种现象更为明显。比如我们有一个这样的示例,如果继续使用物理属性来描述元素的特征,在不同的书写模式下局限性就会显现出来。比如下面这个示例:

尝试着改变writing-mode的值,会得到不同布局效果:

我们可以使用逻辑属性来改变这种现象,比如说我们把widthheight的特理属性的值换成逻辑属性的值inline-sizeblock-size

.wrapper{
    writing-mode: var(--writing-mode);
    inline-size: 60vw;
    block-size: 60vh;
}

除了widthheight有对应的逻辑属性之外,在CSS中的物理属性都能找到相应的逻辑属性,这些逻辑属性常常以 *-block-start*-block-end、***-inline-start*-inline-end**结属,比如下图所演示的margin对应的逻辑属性:

更详细的逻辑属性列表可以查看CSS Logical Properties and Values Level 1规范。

有关于书写模式更多的介绍可以阅读:

网格命名

在我的认知中,我一直觉得CSS Grid是最优秀的Web布局方案,时至今日,CSS Grid已得到众多浏览器的支持。

CSS Grid可以显式的隐式的创建网格线,可以通过网格线来控制网格项目的位置,除此之外,它还有更独特的特性,就是可以给网格线网格区域命名:

另外,在新的网格规范中还引入了subgrid。可以让我们实现网格嵌网格,而且subgrid也会继承父网格的网格线、网格区域的命名等:

可以使用Firefox浏览器调试器的网格查看器来查看网格线相关的命名:

CSS设计的错误列表

这是一份不完整的列表,罗列了一些关于CSS在设计时中不妥的地方

这是一份不完整的列表,但现在所列出的项目仔细阅读还是蛮有意思的。感兴趣的同学可以点击这里查阅

小结

@Rachel Andrew分享的这份PPT非常的长,涉及的内容也比较多,最后以CSS中滚动条相关的特性来做了结尾。可能你看了上面的内容会说,嗯,真香。但还不知道啥时候能用上,其实无需这样担忧,我们应该考虑如何将这些新东西结合起来使用,并且将它们带到工作中来,带到项目中去。

如果你在这方面有不同的感想,欢迎在下面的评论中与我们一起分享。

扩展阅读