页面样式其实大多数情况下都无法速成,需要通过不断地练习、反复地调试才能熟练掌握。但也有一些小伙伴会疑惑为什么写了很久的前端页面,还是写不好页面布局?

其实页面布局有一些很基础的规则,掌握这些规则,很多时候你的疑惑也能迎刃而解。

# 1. 盒模型

盒模型指的是 CSS 基础盒模型。当浏览器对一个文档进行布局的时候,会将每个元素都表示为一个个矩形的盒子,CSS 控制这些盒子的尺寸、属性(颜色、背景、边框等)和位置,渲染引擎则将它们渲染出来。这个模型描述了元素所占空间的内容,每个盒子由四个部分组成:外边框边界(margin)、边框边界(border)、内边距边界(padding)、内容边界(content),如图:

盒模型示意图

我们浏览器对文档进行布局的时候,会将每个元素都表示为图中矩形的盒子。我们使用 CSS 样式来控制这些盒子的尺寸、属性(颜色、背景、边框等)和位置,渲染引擎会依据 CSS 规则树和 DOM 节点生成的渲染树进行渲染。

关于盒模型,这里介绍在开发过程中会遇到的常见问题和应用。

(1) 盒模型会发生margin外边距叠加。当两个或更多个垂直边距相遇时,它们有时被合并(折叠)成单个边距,需要注意的是:

  • 外边距(margin)的高度大小是各个边距中的最大值
  • 行内框、浮动框或绝对定位框之间的外边距不会叠加,相邻的兄弟姐妹、没有内容将父母与后代分开或是空块等三种情况,才会出现外边距叠加

(2) 我们在工作中会常见到一种 CSS3 中新增的盒模型计算方式:box-sizing属性。盒模型默认的值是content-box, 新增的值是padding-boxborder-box。具体的这里不多介绍,大家可以主要关注最常用的border-box,在border-box模型下:

  • 元素宽高(width/height)等于:content + padding + border
  • 布局所占宽高等于元素宽高

# 2. 内联元素与块状元素

内联元素和块状元素在页面中的布局效果有很大的不同,通常我们会使用display来进行调整。

内联元素又称行内元素等,表示位于行内的元素,特点包括:

  • 内联元素只能容纳文本或者其他内联元素,只有这些文本或元素可以与其位于同一行
  • 内联元素的宽度高度不起作用,因此给内联元素设置宽高是不生效的
  • 常见的内联元素:<a>/<span>/<i>/<strong>

块状元素一般是其他元素的容器,可同时容纳内联元素或者其他块状元素,特点包括:

  • 块状元素排斥其他元素与其位于同一行
  • 块状元素的宽度高度起作用
  • 常见的块状元素有:<div>/<p>/<h1><h6>/<ul>/<ol>

我们可以通过display属性去设置元素类型,常用的display属性包括:

  • block:块状元素,可以设置宽度width和高度height
  • inline:内联元素,宽度高度不起作用
  • inline-block:可以理解为块状元素和内联元素的结合
    • 位于块状元素或者其他内联元素内
    • 可容纳其他块状元素或内联元素
    • 宽度高度起作用

使用inline-block可以很方便解决一些问题:使元素居中、给inline元素(<a>/<span>)设置宽高、将多个块状元素放在一行等。

# 3. 元素定位

每一个前端开发都需要认识文档流,关于文档流:正常的文档流也叫普通流,在 HTML 里面为从上到下,从左到右的排版布局。我们常用的布局与position样式属性紧紧相关,其中position样式属性包括:

  • static(默认值):没有定位,元素出现在正常的流中(忽略top/bottom/left/right或者z-index声明)
  • inherit:规定应该从父元素继承position属性的值
  • relative:生成相对定位的元素,相对于其正常位置进行定位,relative特点包括:
    • relative元素保持原有文档流,但相对本身的原始位置发生位移,且占空间
    • relative元素也遵循从上到下,从左到右的排版布局
    • 相对于其正常位置进行定位,在这里设置了relative的元素相对其原本位置(position: static)进行位移
    • relative元素占有原本位置,因此下一个元素会排到该元素后方
    • relative元素占位不会随着定位的改变而改变,也就是说relative在文档流中占有的位置与其原本位置(position: static)相同
  • absolute:生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位。元素的位置通过left/top/right/bottom属性进行规定。absolute特点包括:
    • absolute元素脱离文档流
    • absolute元素不占位,因此下一个符合普通流的元素会略过absolute元素排到其上一个元素的后方
    • absolute元素的定位是相对于static定位以外的第一个父元素进行定位
  • fixed:生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过left/top/right/bottom属性进行规定。fixed的特点:
    • fixed元素脱离文档流
    • fixed元素不占位
    • fixed相对于浏览器窗口来定位,不管是否有static定位以外的父元素
    • absolute元素会随着页面的滚动而滚动,而fixed不会

关于position样式属性在各种场景下的布局效果相对复杂,如果需要熟练掌握还是需要花不少的心思去练习和思考的。

# 4. 元素堆叠

元素的堆叠方式和顺序,除了与position定位有关,也与z-index有关,有关z-index的说明:

  • 当同级元素不设置z-index或者z-index相等时,后面的元素会叠在前面的元素上方
  • 当同级元素z-index不同时,z-index大的元素会叠在z-index小的元素上方

除了同级元素以外,z-index值的设置效果还会受到父元素的z-index值的影响,它只决定同一父元素中的同级子元素的堆叠顺序,在此之外的场景会比较复杂,大家可以自己去实践下,篇幅关系我们不再这里拓展了。

z-index样式属性比较常用于多个元素层级控制的时候,比如弹窗一般需要在最上层,就可以通过设置较大的z-index值来控制。

# 常见页面布局方式

目前来说,前端开发比较常见的布局方式主要有:

  • 传统布局方式
  • Flex 布局方式
  • Grid 布局方式

(1) 传统布局。

传统布局方式基本上借助于上面提到的一些布局规则,结合display/position/float属性以及一些边距、x/y 轴距离等方式来进行布局。

由于文档流、盒模型、等前面都有介绍,这里我们主要补充一下 float 浮动布局方式。给元素的float属性赋值后,元素会脱离文档流,进行左右浮动,紧贴着父元素的边框或者是上一个同级同浮动元素的边框。

首先了解下float属性:

  • float属性定义元素在哪个方向浮动
  • float属性可应用于图像,使文本围绕在图像周围
  • floatblock
    • 设置float浮动的元素自动获取display: block样式
    • 当一个元素浮动之后,不会影响到块级框的布局
  • floatinline-block
    • 当一个元素浮动之后,会影响内联框(通常是文本)的排列和布局
    • float浮动若未指明宽度会尽可能地窄,而inline-block元素会带来空白问题

使用float属性,必然会遇到一个问题:本属于普通流中的元素浮动之后,包含框内部由于不存在其他普通流元素了,也就表现出高度为 0,又称为高度塌陷。

因此,我们也需要掌握 float 撑开父元素的方法:

  • 父元素使用overflow: hidden(此时高度为auto
    • 父元素overflow:hidden后,首先会计算height: auto的真实高度,由于其触发了 BFC,需要包含子元素,所以高度不是 0,而是子元素高度(关于 BFC 大家可以自行去了解一下)
  • 使父元素也成为浮动float元素
    • 将父容器也改成浮动定位,这样它就可以带着子元素一起浮动了
  • 使用clear清除浮动
    • 在浮动元素后方加入clear: both的元素,就可以清除浮动撑开父元素
    • 其中在样式中添加clear:right,理解为不允许右边有浮动元素,由于上一个元素是浮动元素,因此该元素会自动下移一行来满足规则

通过传统方式布局的优势在于兼容性较好,在一些版本较低的浏览器上也能给到用户较友好的体验。但传统布局需要掌握的知识较多也相对复杂,因此 Flex 布局和 Grid 布局也主要用来解决传统布局的种种不便。

(2) Flex 布局。

Flex 布局基于 Flexible Box 模型,通常被称为 flexbox,是一种一维的布局模型。对于 Flex 布局,我们主要需要掌握几个概念:

  • flexbox 的两根轴线:其中,主轴由flex-direction定义,交叉轴则垂直于主轴。
  • 起始和终止:传统布局的文档流是从左到右、从上到下的布局方式。而在 flexbox 中,我们使用起始和终止来描述布局方向和顺序。
  • Flex 容器:采用了 flexbox 的区域(display属性值为flex或者inline-flex)叫做 flex 容器,容器中的直系子元素就会变为 flex 元素。通过flex-direction/flex-wrap/flex等各种属性设置,我们可以方便地设置容器内元素的布局效果。

使用 Flex 布局可以:

  • 通过flex-direction调整 Flex 元素的排列方向(主轴的方向)
  • flex-wrap实现多行 Flex 容器如何换行
  • 使用justify-content调整 Flex 元素在主轴上的对齐方式
  • 使用align-items调整 Flex 元素在交叉轴上如何对齐
  • 使用align-content调整多根轴线的对齐方式

Flex 布局的出现,解决了很多前端开发居中、排版的一些痛点,尤其是垂直居中,因此现在几乎成为了主流的布局方式。以前我们都是基于盒模型来布局,一般使用display属性+position属性+float属性。Flex 布局给flexbox的子元素之间提供了强大的空间分布和对齐能力。

除此之外,还可以对 Flex 元素设置排列顺序、放大比例、缩小比例等等。更多的使用方法,大家可以去网上进行相关的查询和学习。

(3) Grid 布局。

Grid 布局又称为网格布局,提供了一种二维布局的方式,它将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系。

我们知道 Flex 布局是基于轴线布局,与之相对,Grid 布局则是将容器划分成行和列,可以像表格一样按行或列来对齐元素。网格容器的子元素可以自己定位,像 CSS 定位的元素一样有重叠和层次关系。

同样的,对于 Grid 布局,我们也需要掌握几个概念:

  • 网格轨道与行列:一个网格轨道就是网格中任意两条线之间的空间,可以通过grid-template-columnsgrid-template-rows属性来定义网格中的行和列。
  • 网格线:当我们定义网格时,我们定义的是网格轨道,Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素。网格线的编号顺序取决于定义的布局方向,水平网格线划分出行,垂直网格线划分出列。
  • 网格容器:采用了 Grid 布局的区域(display属性值为grid或者inline-grid)叫做网格容器,容器中的直系子元素就会变为网格元素。

使用 Grid 布局可以:

  • 实现网页的响应式布局
  • 实现灵活的 12 列布局(类似于 Bootstrap 的 CSS 布局方式)
  • 与其他布局方式结合,与 css 其它部分协同合作

通过 Grid 布局我们能实现任意组合不同布局,其设计可称得上目前最强大的布局方式,它与 Flex 布局是未来的趋势。其中,Grid 布局适用于较大规模的布局,Flex 布局则适合页面中的组件和较小规模布局。

以上这些内容属于比较基础的布局,我们在写 CSS 过程中会遇到很多的神奇现象,而要理解这些现象,就得知道浏览器布局的一些原理逻辑和设定。除了布局以外,很多页面开发也有对 CSS3 动画的一些要求,这里篇幅关系不多介绍,大家可以通过互联网来学习更多的内容。