(6.2)弹性布局

# 6.2 弹性布局

1.一些约定

(1)在本书中,约定设置display:flex声明或者display:inline-flex声明的元素为“flex容器”,里面的子元素为“flex子项”。

container (flex容器)
	div(flex子项)> img
	div(flex子项)> img
	div(flex子项)> img

为了便于视觉区分,flex容器外部会设置虚线框,flex子项会设置白色到深天蓝色的径向渐变背景,同时图片上会标记原始的序号。

2.宏观介绍

弹性布局相关属性分为4个大的类目,分别是流向控制、对齐设置、顺序控制和弹性设置。

  • 表面flex属性

    “表面flex属性”,就是说最终的布局表现就是CSS属性字面上的意思。这些属性学习难度低,背后细节少,主要的学习成本在语法的记忆上

    • 流向控制:对应flex-flow属性及其相关属性。

    • 对齐设置:相关的CSS属性在弹性布局和网格布局中是通用的,大家可以关注一下6.2.5节的综述。

    • 顺序控制:对应order属性,平常用得不多。

  • 弹性设置:对应flex属性及其相关属性。

6.2.1 设置display:flex声明发生了什么

给任意元素设置display:flex声明或者display:inline-flex声明,弹性布局就会被创建。display:inline-flex声明可以让flex容器保持内联特性,也就是可以让图片和文字在一行显示,多用在精致的小控件布局中,至于其他特性表现则和display:flex声明一模一样。

1.flex子项块状化

<div class="container">
<content>1</content>
<content>2</content>
<content>3</content>

</div>

<!--container 没设置flex时,content是inline,设置后 content 为 block-->
. container {
display: flex;
}

默认状态下,<content>元素的display计算值是inline,而在上面这个例子中,<content>元素的display计算值是block,其变成了块级元素。

flex子项都是块级水平元素,因此,在flex子项元素中使用vertial-align(使行内元素盒模型与其行内元素容器垂直对齐) 属性一定是没有任何效果的。

vertial-align:

该属性定义 行内元素 的基线相对于该元素所在行的基线的垂直对齐。允许指定负长度值和百分比值。这会使元素降低而不是升高。在表单元格中,这个属性会设置单元格框中的单元格内容的对齐方式。

flex子项的块状化特性对匿名内联元素同样适用,所谓匿名内联元素指的就是没有嵌套标签的裸露的文本元素,例如下面HTML代码中的字符2就是匿名内联元素

    <style>
        .container {
            display: flex;
            outline: 1px dotted;
            counter-reset: index;
            width: 320px;
        }

        .container>content {
            display: flex;
            background: radial-gradient(circle, white, deepskyblue);
            width: 100px;
            height: 100px;
            justify-content: center;
            align-items: center;
        }
    </style>    
	<div class="container">
        <content>1</content>
        2
        <content>3</content>
    </div>

此时字符2会变成匿名块级元素,和前后两个<content>元素并排显示,如图6-15所示。但是,如果仅是空格字符(Enter空格、Space空格或Tab空格),则不会渲染,即使设置white-space:pre或者white-space:pre-wrap也是如此。

2.flex子项浮动失效

这是弹性布局中的一个常识,但是很多人都不知道。例如,下面<content>元素设置的float:leftfloat:right都是无效的:

    <div class="container">
        <content style="float: left;">1</content>
        <content>2</content>
        <content style="float:right;">3</content>
    </div>

控制flex子项左右对齐是有专门的CSS属性的。

3.flex子项支持z-index属性

即使flex子项的position属性的计算值是static,flex子项也是支持 z-index 属性的。如果z-index属性值不是auto,则会创建新的层叠上下文。

4.flex子项的margin值不会合并

flex子项的 margin 值是不会合并的,这一点和普通的块级元素不一样。

5.flex子项是格式化的尺寸

flex子项的尺寸是一种格式化的尺寸,也就是经过精确计算的、应用某个计算值后的尺寸,具体的尺寸计算规则比较复杂,在 flex-basis 属性那里会进一步讲解。在这里,我们只需要知道flex子项的尺寸在底层是有具体的计算值的,因此,我们可以使用 margin:auto 进行剩余空间的智能分配(如果这里的因果关系看不明白,请参考《CSS世界》一书的4.3.4节)。

6.2.2 flex-direction 属性与整体布局方向

flex-direction 属性用来控制flex子项整体布局方向,决定是从左往右排列还是从右往左排列,是从上往下排列还是从下往上排列。flex-direction属性的语法如下:

flex-direction: row | row-reverse | column | column-reverse;
  • row是默认值,表示flex子项显示为水平排列。方向为当前文档水平流方向,默认情况下是从左往右排列;如果当前水平文档流方向是rtl(如设置direction:rtl),则从右往左排列。

  • row-reverse表示flex子项显示为水平排列,但方向和row属性值相反。在默认文档流下的排版效果如图6-19所示,可以看到图片元素从右往左排列。

  • column表示flex子项显示为垂直排列。默认情况下的排列顺序是从上往下排列,使用writing-mode属性可以改变这个排列顺序。属性值column在移动端垂直排版布局中经常被使用。

  • column-reverse表示flex子项显示为垂直排列,但方向和column属性值相反。

6.2.3 flex-wrap属性与整体布局的换行表现

flex-wrap属性用来控制flex子项是单行显示还是换行显示,以及在换行情况下,每一行内容是否在垂直方向的反方向显示。flex-wrap属性比较好记忆,在CSS世界中,只要看到单词“wrap”,则一定是与换行显示相关的操作,例如word-wrap属性、white-space:nowrap声明或者pre-wrap值等。flex-wrap的语法如下:

flex-wrap: nowrap | wrap | wrap-reverse;
  • nowrap是默认值,表示flex子项是单行显示,且不换行。由于flex子项不换行,因此可能会出现子项宽度溢出。此时,可以为图片设置max-width:100%来避免宽度溢出,原因在于max-width属性的优先级大于width属性的优先级,能够使图片尺寸从固定值变成相对值,flex子项的最小尺寸不再是100px,flex子项弹性收缩就可以生效了

  • wrap表示flex容器宽度不足的时候,flex子项会换行显示。由于flex容器宽度不足以放下4个图片元素,于是第四张图片就换行显示了。

  • wrap-reverse表示宽度不足的时候,flex子项会换行显示,但是flex子项是从下往上开始排列的,也就是原本换行到下面一行的flex子项现在换行到上面一行,第四张图片在第一行显示了。

6.2.4 熟练使用flex-flow属性

flex-flow属性是flex-direction属性和flex-wrap属性的缩写,表示弹性布局的流动特性。

flex-flow属性的语法如下:

flex-flow : <'flex-direction'> || <'flex-wrap'>

当多属性值同时使用的时候,使用空格分隔,且不区分前后顺序。举个例子,容器元素设置如下:

.container {
	display: flex;
	flex-flow: row-reverse wrap-reverse;
}

可以看到水平排列顺序是从右往左,垂直排列顺序是从下往上,这和默认的排列方向完全相反。

在日常开发的时候,我们没有必要使用flex-direction属性和flex-wrap属性,直接使用flex-flow这个缩写属性就好了。flex-direction属性和flex-wrap属性支持的所有值flex-flow属性都支持,例如下面的语法都是合法的:

/*flex-flow: <'flex-direction'>*/
flex-flow: row;
flex-flow: row-reverse;
flex-flow: column;
flex-flow: column-reverse;

/* flex-flow: <'flex-wrap'>*/
flex-flow: nowrap;
flex-flow: wrap;
flex-flow: wrap-reverse;

/*<'flex-direction'>和<’flex-wrap'>*/
flex-flow: row nowrap;
flex-flow: nowrap row;
flex-flow: column wrap;
flex-flow: column-reverse wrap-reverse;

6.2.5 CSS全新的对齐特性综述

从这里开始,会陆续介绍多个CSS对齐相关属性。例如在弹性布局这一节中,会介绍justify-contentalign-items和align-content(作用在flex容器上的CSS属性),以及align-self(作用在flex子项上的CSS属性);

在6.3节的网格布局中会介绍更多的CSS对齐属性,包括justify-items和justify-self等属性。这些CSS属性名称都是几个固定单词的组合,且这些单词在整个CSS世界中是有通用的含义的,具体如下:

  • justify表示水平方向的样式设置;

  • align表示垂直方向的样式设置;

  • items表示全体元素的样式设置;

  • content表示整体布局的样式设置;

  • self表示元素自身的样式设置,其一定是应用在子元素上的。

因此,justify-content属性就表示整体布局的水平对齐设置,align-items就表示全体元素的垂直对齐样式设置。

重点来了。虽然这些CSS对齐属性仅在弹性布局和网格布局中有效(以后可能会扩展到其他布局中),甚至有些CSS对齐属性仅在网格布局中有效,但是就语法而言,这些属性在所有布局场景中都是合法的。例如给<body>元素设置下面的CSS代码虽然没有任何效果,但是语法上是没有问题的:

body {
	place-items: center;
}

明白这一点很重要,因为如果我们看W3C规范或者MDN文档,会发现一个普通的对齐属性或者一个普通的关键字属性值的释义都是非常复杂的。例如align-items:normal声明,会有大段内容描述其在绝对定位布局下的表现,绝对定位的描述还分替换元素和非替换绝对定位元素,也会有大段内容描述其在弹性布局下的表现,还会有大段内容描述其在网格布局下的表现,看得人头晕。实际上,完全没有那么复杂!

CSS所有新特性的设计都有一个必须向前兼容的准则,即align-items属性是一个全新的CSS属性,那它的各种特性就必须兼容各种传统布局的尺寸模型,也就必须解释对齐属性的默认值在传统布局场景下的计算规则,使其符合CSS2.1中的定义的行为,这才导致规范对属性值的解释过于复杂和怪异。

甚至有些解释在我看来就很牵强,例如,W3C规范文档指出:“对于display:table-cell元素,align-content:normal的行为表现取决于vertical-align属性,如果vertical-align属性值是top,则align-content属性的表现为start,如果vertical-align属性值是middle,则align-content属性的表现为center,如果vertical-align属性值是bottom,则align-content属性的表现为end,如果vertical-align属性值是其他值,则align-content属性的表现为baseline。”

乍一看,还以为单元格元素可以使用align-content属性控制垂直对齐。实际上,虽然弹性布局和网格布局支持align-content属性已经很多年了,但是单元格元素到现在依然不支持align-content属性中任何对齐值。一是没必要,二是对浏览器厂商来说是一个较大的挑战,因为需要修改过去几十年一直运行良好的CSS解析模块,这样很容易出现新的渲染bug。因此,根据我的判断,单元格元素或者绝对定位布局支持新的CSS对齐属性,至少最近5年是不会出现的。

因此,对于CSS新世界中的对齐属性,大家就只盯着弹性布局和网格布局就可以了。块级元素该如何表现,绝对定位布局该怎么表现,单元格该如何表现,这些问题目前都可以放在一边,完全不用考虑。

另外,很多文档把CSS对齐属性支持的所有属性值都罗列到了一起,这对学习是非常不友好的,例如6.2.6节要介绍的justify-content属性,其支持的属性值包括下面这些(尚不包括众多组合写法):

justify-content: normal | flex-start | start | left | flex-end | endright| center | stretch | 
space-between | space-around | space-evenlysafe | unsafe | baseline | first-baseline | last-baseline;

默认值指的是当前布局下的默认值,而非语法上的初始值。例如,虽然在语法上justify-content属性的初始值是normal(以前是auto),但是在弹性布局中,normal的对齐表现是flex-start,因此,在表述的时候,会称flex-start是弹性布局中justify-content属性的默认值;在网格布局中,normal的对齐表现是stretch,因此,在表述的时候,会称stretch是网格布局中justify-content属性的默认值。

6.2.6 justify-content属性与整体布局的水平对齐

justify-content: normal| flex-start | flex-end | center | space-between | space-around | space-evenly ;

normal是初始值,表示根据环境不同,可以采用不同的对齐表现。如果有列的概念,normal的行为类似与stretch,例如网格布局和分栏布局;如果没有列的概念,例如,在flex容器中,normal的行为表现类似于start(规范的说法,实际应该是flex-start)。

flex-start可以看成默认值,它是一个逻辑CSS属性值,与文档流方向相关,默认表现为整体布局左对齐。

需要注意的是该值是flex-start,不是start(目前start在弹性布局中无效,以后可能会支持),而网格布局中的justify-content属性支持的是start。(弹性布局中其他逻辑属性值也都是以flex-开头,网格布局中并没有这样的特性表现,这个细节大家可以专门记一下。)

flex-end是逻辑CSS属性值,与文档流方向相关,默认表现为整体布局右对齐。

注意,如果flex容器设置了overflow滚动,同时应用justify-content:flex-end,滚动效果会失效。

center表现为整体布局居中对齐。这个属性值多用在弹性布局有多行且个数不确定的场景下。为了让最后一行居中显示,需要设置justify-content:center。

space-between两端对齐。

表示多余的空白间距只在元素中间区域分配,可以看到3个flex子项的中间有两处宽度一致的空白间距,视觉上表现为效果。

space-around边缘两侧的空白只有中间空白宽度的一半。

表示每个flex子项两侧都环绕互不干扰的等宽的空白间距,最终在视觉表现上边缘两侧的空白只有中间空白宽度的一半,效果如图6-29所示,可以看到3个flex子项的两侧都环绕了空白间距,最终的空白间距比例是1:2:2:1。

space-evenly表示每个flex子项两侧空白间距完全相等。

可以看到3个flex子项的左右两侧的空白间距都是一样的,最终的空白间距比例是1:1:1:1。

6.2.7 垂直对齐属性align-items与align-self

align-items属性和align-self属性

一个区别是align-self属性是设置在具体的某一个flex子项上的,而align-items属性是设置在flex容器元素上的,控制所有flex子项的垂直对齐方式;

另一个区别是align-self属性的初始值是auto,其余的属性值align-items属性和align-self属性的是一样的,含义也是一样的。

align-items: stretch | flex-start | flex-end | center | baseline;
align-self: auto | stretch | flex-start | flex-end | center | baseline;

一张align-items简图

auto是align-self属性的默认值,表示flex子项的垂直对齐方式是由flex容器的align-items属性值决定的。

stretch可以看成弹性布局中align-items属性的默认值,表示flex子项在垂直方向上拉伸。

flex子项的白色到深天蓝色的径向渐变背景区域是上下占据整个flex容器的,它可以让flex子项的高度拉伸到容器高度。如果flex子项设置了具体的高度值,则按照设置的高度值渲染,而非拉伸,也就是stretch值渲染尺寸的优先级小于height等属性。

flex-start是逻辑CSS属性值,与文档流方向相关,默认表现为flex子项顶部对齐,

可以看到白色到深天蓝色的径向渐变背景区域不再是拉伸,而是适应图片的高度,同时,每一行的flex子项都是顶部对齐的。

flex-end是逻辑CSS属性值,与文档流方向相关,默认表现为flex子项底部对齐。

可以看到每一行的flex子项都是底部对齐的。

center表示flex子项都是垂直居中对齐。

可以看到每一行的flex子项都是垂直居中对齐。

baseline表示flex子项参与基线对齐。

注意这里的措辞是“参与基线对齐”,并不是指flex子项和基线对齐,而是让所有flex子项的内外基线都在一条水平线上。

如果flex子项没有对应的基线,则沿着flex子项的边框盒子线对齐。

6.2.8 align-content属性与整体布局的垂直对齐

align-content属性和align-items属性的区别在于align-items属性设置的是每一个flex子项的垂直对齐方式,而align-content属性将所有flex子项作为一个整体进行垂直对齐设置。

align-content: stretch | flex-start | flex-end | center | space-between | space-around | space-evenly;

align-content属性定义了「多根轴线」的对齐方式。如果项目只有一根轴线,该属性不起作用

stretch(默认值):轴线占满整个交叉轴

Image

flex-start:与交叉轴的「起点对齐」

Image

flex-end:与交叉轴的「终点对齐」

Image

center:与交叉轴的中点对齐,也就是「垂直居中」

Image

space-between:与交叉轴两端对齐,轴线之间的间隔「平均分布」

Image

space-around:每根轴线「两侧的间隔都相等」。所以,轴线之间的间隔比轴线与边框的间隔大一倍

Image

6.2.9 order属性与单个子项的顺序控制

我们可以通过设置order属性来改变某一个flex子项的排序位置。order属性的语法如下:

order: <integer>; /*整数值,默认值是О*/

所有flex子项的默认order属性值是0,因此,如果我们想要某一个flex子项在最前面显示,则可以设置比0小的整数(如−1)就可以了,代码如下:

image-20220502175308313

6.2.10 必读:深入理解flex属性

flex属性是弹性布局的精髓,因为弹性布局的“弹性”就是flex属性的作用,所以如果大家想成为弹性布局高手,本节的内容值得反复阅读。

语法

flex: auto /*1 1 auto*/
flex: none /*0 0 auto*/
flex: 1 /*1 1 0%*/
flex: <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> 

其中双管道符“||”表示或者,可以无序,也可以同时存在;问号“?”表示0个或1个,也就是flex-shrink属性可有可无

/*1个值,flex-grow */
flex: 1;

/*1个值,flex-basis */
flex: 100px;

/*2个值,flex-grow和flex-basis */
flex: 1 100px;

/*2个值,flex-grow和flex-shrink */
flex: 1 1;

/*3个值*/
flex: 1 1 100px;

3.理解flex-grow属性、flex-shrink属性和flex-basis属性

flex-grow

flex-grow属性定义项目的放大比例,默认为0,即如果「存在剩余空间,也不放大」

当其中的一个项目设置了flex-grow1时,它将占据剩余空间的100%

Image

如果所有项目的flex-grow属性都为1,则它们将「等分剩余空间」(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

flex-shrink

flex-shrink属性定义了项目的缩小比例,「默认为1」,即如果空间不足,该项目将缩小,和flex-grow算是相对的属性,一个放大一个缩小

Image

如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,属性为0的不缩小。

如果都是默认值1,则空间不足时,「等比缩小」

注意:负值对该属性无效。

为了让这3个属性更容易被理解,我们不妨把弹性布局中的尺寸分配看成分配家产。

flex-basis

flex-basis属性定义了在分配多余空间「之前」,项目占据的主轴空间(在基本概念中有介绍到)。浏览器根据这个属性,计算主轴是否有多余空间。

  • 它的默认值为auto,即项目的本来大小。

简单来说,当设置了flex-basis以后,就设定了「项目的尺寸」

「注意」widthflex-basis的区别是,flex-basis的优先级更高。如果设置的flex-basis的值不为auto,那么width设置什么值都无效,以flex-basis的值为准。只有当flex-basis的值为auto的时候,该项目才会是width设定的值。

「注意」flex-basiswidthauto值,那最后的空间就是根据内容多少来定的,内容多占据的水平空间就多

Image

可以看出在item2分配剩余空间之前,item1先占据了300px

案例

范某的家产分配遗嘱是在自己50岁时候制定的。由于范张、范鑫和范旭都已经成家立业,每人100万元;

而范帅和范哥尚未成年,和范某还住在一起,所以,遗嘱就是剩下的家产由范帅和范哥两人平分,范帅和范哥也分得人均100万元的家产。

但是世事难料,没过几年范家家道中落,范某总资产已经不足300万元,此时,扣除答应范张、范鑫和范旭的300万元,已经没有多余家产了,范帅和范哥就没有家产可继承了,这就相当于容器剩余空间不足。

<div class="container">
    <item class="zhang">范张</item>
    <item class="xin">范鑫</item>
    <item class="xu">范旭</item>
    <item class="shuai">范帅</item>
    <item class="ge">范哥</item>
</div>

我们来看一个案例,请实现范张、范鑫和范旭每人有100万元固定家产,范帅和范哥有20万元保底家产。

如果范某去世那天家产还有富余,则范帅和范哥按照3:2比例分配;

如果没有剩余家产,则范张、范鑫和范旭按照2:1:1的比例分别给范帅和范哥匀20万元保底家产。

/*老大不会争夺多余家产,但是会在家产不足时分出2倍于老二和老三分出的家产,这是作为老大应有的姿态*/
.zhang {
    flex: 0 2 100px;    
}
/*老二和老三不会争夺多余家产,但是会在家产不足时分出部分家产,照立老四和老五这里也可以直接写成flex: 100px*/
.xin,
.xu {
    flex: 0 1 100px;    
}

/*老四会争夺多余家产,且会在家产不足时获得哥哥们分出的家产,确保能够活下去,感谢3位哥哥的照顾*/
.shuai {
    flex: 3 0 20px;    
}

/*老五会争夺多余家产,不过拿到的比哥哥少一点,且会在家产不足时获得哥哥们分出的家产,感谢哥哥们的照顾*/
.ge {
    flex: 2 0 20px;    
}

如果flex容器尺寸足够,范帅和范哥会按照flex-grow属性3:2的比例分配剩余空间

image-20220502204158098

随着flex容器尺寸变小,范帅和范哥可支配的剩余空间也越来越小,直到接近flex-basis属性设置的保底的20px尺寸

image-20220502204205041

在线练习flex布局

FLEXBOX FROGGY 这是一个在线练习flex布局的小游戏,在游戏中通过使用flex代码来给青蛙送到指定的位置上。
请添加图片描述

posted @ 2022-02-13 19:53  【唐】三三  阅读(296)  评论(0编辑  收藏  举报