CSS是为图文信息展示而诞生的。如果让你构建一个CSS世界,要求是非常便于图片和文字的呈现,你会怎么构建?本文总结了《CSS世界》《CSS新世界》中张鑫旭老师对CSS世界的理解。从CSS世界出发,重新去理解什么是盒模型?布局怎么影响一个个盒子?以及为什么要这样设计?

1. CSS世界

在CSS世界中,HTML是魔法石,选择器就是选择法器,CSS属性就是魔法师,CSS各种属性值就是魔法师的魔法技能,浏览器就是他们所在的“王国”。CSS 作用于 HTML 元素上,各种布局影响了元素的展示,从而构建出一个个丰富的页面。

2. 盒模型

每个HTML元素都有外在盒子和内在盒子。

  • 外在盒子
    负责元素是可以一行显示,还是只能换行显示
  • 内在盒子
    负责宽高、内容怎么呈现。width/height作用在内在盒子上
    元素的 display 属性可以理解为 display: 外在盒子-内在盒子。
    display: inline-block,它的外在是inline盒子,一行显示。内在是block盒子,允许设置宽高。

2.1 外在盒子

外在盒子可以分成这几类:块级盒子,内联盒子,特殊盒子,表格盒子

外在盒子 display display: 外在-内在 表现 常见元素
块级盒子 display: block display: block-block 换行显示 <div><p><h1>
内联盒子 display: inline-block display: inline-block 一行显示,可以设置宽高 手动设置css属性
display: inline display: inline-inline 一行显示,无法设置宽高 <span><a><img><input>
特殊盒子 list-item 让元素额外显示符号 <ul><li>
表格盒子 display: table display: block-table 换行显示 <table>
display: inline-table display: inline-table 和文字图片一行显示 手动设置css属性

display: inline-table示例


2.2 内在盒子

内在盒子,也叫容器盒子。width/height会作用在容器盒子上。
内在盒子分成了4个盒子,分别是content box、padding box、border box和margin box。

box-sizing,是改变 width / height 的作用细节。box-sizing的值有

  • content-box 让宽度作用在content box上(默认值)
  • border-box 让宽度作用在border box上(width = content + padding + border)。对应JS的 offsetWidth, offsetHeight
    (注意:JS中的 clientWidth, clientHeight 对应padding box的尺寸)


width的默认值是auto,如何理解auto?

  • 充分利用空间:<div>、<p>元素的宽度默认是100%于父级容器的。display: block 的元素不用额外加 width: 100%
  • 收缩与包裹:inline-block, table会包裹元素内容至合适宽度,理解为fit-content
  • 收缩到最小:当空间不够的时候,如很多列的table有的列会收缩为一行,理解为min-content
  • 超出容器限制:一般子元素都不会主动超过父元素的容器宽度,除非内容是很长的连续英文和数字
  • 如非必要,遵循无宽度准则。因为元素一旦设置了宽度,就失去了流动性
  • 注意,父级没有具体高度值的时候,height: 100% 是无效的。

3. 内联元素与流

有了盒子,如何排列盒子?这个时候就要引出“流”的概念。“流”是CSS世界中一种定位和布局机制,它是一条引导元素排列和定位的、看不见的“水流”。流作用于外在盒子,块级盒子负责结构,内联盒子接管内容。块级盒子的流很简单,就是一行行往下排。而下面介绍的内联布局,主要是为图文内联元素设计的,毕竟CSS是为图文信息展示而诞生的。

内联布局比较重要的两个问题:对齐和行高。当一堆内联元素放在同一行:

  1. 它们在行上如何对齐?
  2. 已知 display: inline 元素无法设置width/height,那应该怎样去调整它们的高度?

3.1 vertical-align 基线

先回答第一个问题,如何对齐?
在各种内联相关模型中,凡是涉及垂直方向的排版或者对齐的,都离不开最基本的基线(baseline)。例如,line-height行高的定义就是两基线的间距,vertical-align的默认值就是基线。

什么是基线?

  • vertical-align: baseline:基线,字母x的下边缘线。内联元素默认是基线对齐的

什么是中线?

  • vertical-align: middle:中线,基线往上 1/2 * x-height 的高度,近似x的交叉点位置。vertical-align:middle并不是绝对的垂直居中对齐。因为大部分字体都要往下偏一点。
  • x-height:指小写字母x的高度。


3.2 line-height 占据高度

vertical-align提供了内联元素垂直对齐的功能。
再来回答第二个问题,已知 display: inline 元素无法设置width/height,那应该怎样去调整它们的高度?

此时,有一个空的<div>,高度是0,我们在里面写上几个文字,<div>高度就有了,这个高度由何而来,或者说是由哪个CSS属性决定的?

不少人会认为<div>高度是由里面的文字撑开的,也就是font-size决定的,但本质上是由line-height属性全权决定的。line-height如何通过改变行距实现文字排版?
line-height: 2: 两行文字中间的间隙一个文字尺寸大小
line-height: 1: 两行文字会紧密依偎在一起
line-height: 0.5: 两行文字就是重叠纠缠在一起


3.3 空白幽灵节点

line-height会影响内联元素(文字)的占据高度,那替换元素<img>和 inline-block 内联块级元素呢?
下面例子中, box里只有一个img元素。<img>元素的高度是128px, box元素的高度是256px。line-height把图片占据的高度变高了吗?

1
2
3
4
5
6
.box {
line-height: 256px;
}
<div class="box">
<img src="1.jpg" height="128">
</div>

答案是:
line-height不能影响<img>的占据高度。而是把“幽灵空白节点”的高度变高了。<div><img /></div>会在<div>内构成一个“行框盒子”,在HTML5文档模式下,每一个“行框盒子”的前面都有一个宽度为0的“幽灵空白节点”,其内联特性表现和普通字符一模一样,所以,这里的容器高度会等于line-height设置的属性值256px。

4. 流的破坏(float, absolute)

至此,内联布局、流布局解决了大部分图文排版的问题。但它们总是方方正正的,有时候我们希望有一些特殊的布局,如文字环绕效果,或者元素固定在某个位置,要实现这样的效果,正常流就有些捉襟见肘。因此,CSS使用float, absolute 通过破坏正常流来实现一些特殊的样式表现。

  • float
    浮动的本质就是为了实现文字环绕图片效果。float 通过破坏正常CSS流实现CSS环绕,脱离了正常流,让父元素高度坍塌。
  • absolute
    同样具有破坏性的是position: absolute | fixed | relative实现精确定位

5. 传统CSS布局

总结一下上面的内容,CSS 2.1 定义了4种布局方式:

  • 块布局:元素以 display: block 的方式布局,负责布局结构
  • 内联布局:元素以 display: inline 的方式布局,负责展示文本内容
  • 表格布局:元素以 display: table(inline-table) 布局,用于布局二维表格数据
  • 定位布局:元素以 position: absolute 定位, 不考虑文档中其他元素,用于精确定位

这4种布局都是为图文信息展示而生。但随着Web页面越来越复杂,二维布局的需求越来越多,用这4种方法实现一个左侧栏右内容的布局,我们会:

  • display:inline-block让div元素变成内联块
  • position: absolute 精确定位加上 margin-left: -200px
  • float实现

但它们都不是专门为二维结构设计的。在这种背景下,全新的CSS布局模式出现。它们就是 flex 弹性布局和 grid 网格布局。
这两种布局专门为现代Web应用设计,代码简单,样式丰富,逐渐成为主流的布局方式。

6. flex 弹性布局

flex 弹性布局通过流向控制、对齐设置、空间分配实现布局效果。

6.1 流向控制

  • flex-direction 布局方向
    控制子项是从左往右、从右往左,从上往下,从下往上排列
  • flex-wrap 换行表现
    控制子项是单行显示还是换行显示
    • nowrap:单行显示,且不换行,可能会出现子项宽度溢出
    • wrap:当容器宽度不足时,子项会换行显示

6.2 对齐设置

  • justify-content 水平对齐
  • align-items 垂直对齐

6.3 空间分配

我们可以把弹性布局的空间分配看成分配家产

  • flex-basis 分配家产的基础数量
    相当于width
  • flex-grow 家产有富余时如何分配
    默认值是0,表示多余空间不分配。所有剩余空间总量是1,如果flex-grow是0.3,表示分配剩余空间 * 0.3。如果大于1,则独享所有空间。如果子项都设置了flex-grow,总数>1,则按值/总数来分配
  • flex-shrink 家产不足时如何收缩
    默认值是1,如果空间不足,子项等比例收缩,填满flex容器。一个子项的flex-shrink为0,其余为默认1,那么空间不足时,该项目不收缩,其他都收缩。

flex 是 flex-grow, flex-shrink, flex-basis 三个属性的缩写

6.4 顺序控制

通过 order 改变子项的排列顺序,数值越小,排列越靠前。默认为0

7. grid 网格布局

网格布局(Grid)是很强大的 CSS 布局方案。
Grid 布局将容器划分出”行”和”列”,产生一个个单元格,然后指定”项目所在”的单元格,从而做出各种各样的布局。

7.1 划分单元格

  • grid-template-columns 定义每一列的列宽
  • grid-template-rows 定义每一行的行高
  • grid-gap: 20px 20px 定义行与行之间的间距,列与列的间距

7.2 指定区域

  • grid-template-areas 定义区域,多个单元格可以合并区域
    1
    2
    3
    4
    5
    6
    7
    8
    .container {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    grid-template-rows: 100px 100px 100px;
    grid-template-areas: 'a a a'
    'b b b'
    'c c c';
    }

参考资料