所谓布局,就是控制网页页面上的各块内容,将它们放在我们想要放置的位置,搭建一个视觉骨架。
Web 技术在开始刚发明出来时只是用作文档资源的共享,并没有对布局进行过多考虑。
所以,最开始的布局就没有布局,网页就是现实文档的电子化,网页元素基本只有两种类型:行内元素(对应现实文档的一行行的字)和块级元素(对应现实文档的一个个段落)。
此时的布局就是块级元素横从上到下排列,行内元素处于块级元素内部,从左到右延升,如果设置了换行属性,在到达网页边缘时进行换行。
这种原始布局就是浏览器的默认布局形式:Normal Flow,中文译作标准文档流。
在标准文档流下,如果不借助其他技术,开发者对于布局是基本没有任何控制手段的。
1. display
属性
在 flexbox
和 grid
布局模式出现之前, display
在布局方面的作用一般是用来改变浏览器对待元素的显示类型,是按行内元素显示,还是按块级元素显示。
display: inline; // 行内显示
display: block; // 块级显示
值得一提的是,使用 display 改变 HTML 元素的显示形式并不会改变它们的 HTML语义,比如可以让一个块级元素按照行内元素的显示方式来显示,但它还是一个块级元素,而不是一个行内元素。
但无论怎样改变一个元素的显示类型,在不改变 布局模式 的情况下,无法实现复杂的布局。
比如,让页面内容按照两列来显示,这在 Normal Flow(块级元素只能从上到下排列)布局下是无法实现的。
所以必须借助其他手段。
2. Table 布局
Table 是一种二维数据结构,用来展示表格数据,但是在web早期时代却天然的成为了一种合适的布局技术,因为那个时候 CSS 刚刚发明没多久,对布局还没有很好的支持。
而 HTML自带的表格标签组 能很好地将页面内容放在一个个不同的单元格里(或是一些合并后的单元格),通过Table 布局理论上可以实现任意的布局方案。
但是Table布局也有很严重的缺点:
- 不具备弹性:只能根据内容提前设置好布局形式,不能根据内容本身弹性调整布局。
- 需要写大量多余的 Table 相关标签,使得HTML源码繁杂丑陋。
- 非常难Debug和维护。
- 破坏了网页内容的语义。
由于这些缺点,Table布局技术被淘汰。但是它的核心布局思想是很优秀的,所以也就有了后来的继承者栅格布局技术 grid
。
除此之外,Table 布局技术后来也被吸纳进 CSS 里面,我们可以通过 display: talbe
来使一个元素内部的子元素按照 Table 进行布局。
但是与其他更优秀的布局技术相比,这种方式无疑是不太受欢迎的。
3. Floats 布局
Float 和 Table 一样,也是一个被用歪了的技能,它一开始只是用来实现类似文字环绕一张图片的效果,如下图所示。
对一个元素使用 Float 会使它脱离标准文档流往左飘或往右飘,可以让标准文档流中不可能处于同一行的块级元素能够处于同一行。
由此我们可以借由 float 实现布局目的。
但应该明白的是,float 布局并没有改变网页的默认布局模式,也不是使用了一种新的布局模式。
float 布局的本质在于将要布局元素的放在了一个新的图层中:
使用 float 可以实现布局,但也带来了一些 副作用,比如浮动元素如果高度比父元素大,那么会产生溢出现象:
而为了解决这些副作用,人们又想出了一系列 hack,比如清除浮动,给父元素设置 overflow: auto
等等让人一眼看过去不知道为什么要这么做的奇技淫巧。
从此,苦逼的开发者们又多了一堆面试要背的八股文。
这一系列的本质原因就在于 float 本来就不是用来进行布局的工具,只是那个时候没有比它更好的工具了。
4. 栅格系统
在 float 的基础上人们发明了栅格布局系统,最有名的当属 Bootstrap 栅格系统 (Grid System),利用强大的栅格系统可以实现很多复杂的布局。
栅格系统的实现原理是使用 float 让块级元素排在同一行,利用 margin 来控制列与列之间的空隙 (gutter)。
一般这种利用 float 创建的栅格系统都会有一个 wrapper 或者 container 来充当栅格系统最外围的容器,接着是每一行的容器 row,最后是具体的列 column 。除了 column 外,还有 gutter 也就是列与列之间的空隙。
大多数栅格系统一般设置 column 的数量为 12 ,因为 12 可以被 6,4,3,2 整除,因此适用范围较广。
这个 wrapper 一般都会有一个最大的宽度限制,当屏幕宽度大于这个最大宽度限制时,使用 margin: 0 auto
让其居中。
.container {
width: 100%;
max-width: 960px;
margin: 0 auto;
box-sizing: border-box;
}
对于列来说,为了实现响应式设计,一般都会使用百分比来控制其宽度:
.column {
width: 6.25%;
float: left;
box-sizing: border-box;
}
那么如何计算每个列宽应该占据多少百分比呢?这里有一个具体的计算公式:
result = target / context
在这个公式中,result 就是我们要求的百分比,target 表示每个具体的列宽,context 表示容器的宽度。
举个例子,假设我们的 column 宽度是 60px,然后容器 container 的宽度是 980px,那么百分比为:
60 / 960 = 0.0625
那么当容器宽度小于 960px 时,列宽都会按着这个百分比占据合适的空间。
而当容器宽度大于 960px 时,列宽就固定在 60px。
但是这种栅格系统也有它的缺点,比如最大的缺点就是跟表格是二维的相比,它本质上是一维的。
也就是说我们可以在水平方向上控制某个元素占多大的宽度,但是我们很难在竖直方向上控制这个元素的高度,除非我们给其设置一个固定的高度。
如果某一列的内容过多,它会向下延升,也很容易破坏整体的布局。
还有,在每一行中,设置的 column 的数量不能超过栅格系统所允许的最大量,当超过时,布局会崩坏。
除此之外,我们得保证所有 column 的宽度和空隙的宽度加起来不能超过容器 container 的宽度,否则,布局也会崩坏。
另外值得一提的是,就算是 Bootstrap,后面也抛弃了采用 float 作为栅格系统的基础技术,转而使用了新的布局模式:Flexbox,可以查看 Bootstrap 相关文档。
5. Positioning
接着我们来讲定位技术,定位在布局技术这个领域是充当着 辅助 的角色的。
定位总共有5种模式:
static
无定位relative
相对定位absolute
绝对定位fixed
固定定位sticky
一种可以在static
和fixed
之间转变的定位模式
我们一般使用 绝对定位 和 固定定位 来将某个页面内容来放在我们想要的某个特定的位置上。
其实现的原理跟浮动类似,将元素从标准文档流中拿出来,然后放置在 不同的图层 ,通过调整其位置来实现目的。
6. 多列布局 Multiple-column layout
从这一节开始的布局技术都是属于现代的布局技术,上面这些布局技术除了 定位 外,理论上都是属于应该被淘汰的布局技术。
但是由于历史包袱的原因,很多项目所采用的还是这些过时的布局技术,所以了解和掌握这些布局技术也是有必要的。
对于很多前端开发者来说,多列布局 Multiple-column layout 听起来是一项比较陌生的技术,但它确确实实是一种现代布局技术。
通过使用多列布局,我们可以轻松实现类似报纸杂志或论文上面的多列布局:
<div class="container">
<article>
<p><!--一个段落--></p>
<p><!--一个段落--></p>
<blockquote>
<p><!--一个段落--></p>
</blockquote>
<p><!--一个段落--></p>
</article>
</div>
.container article {
// 多列布局属性
column-count: 2;
column-gap: 2em;
column-rule: 1px solid #ccc;
}
上面多列布局例子来源于文章:When And How To Use CSS Multi-Column Layout
7. Flexbox
接下来就是我们现在熟悉的 flexbox 弹性盒布局技术了。
在以前很长的一段时间中,CSS 唯一可靠的布局技术就是上面提到的 float + position,它们虽然能用,但还是有很多缺点和困难,比如:
- 怎么垂直居中一个元素?
- 怎么让一个容器的子元素都占据相同的宽度?
- 怎么在不设置具体高度的情况下让同一行的子元素拥有相同的高度?
而 Flexbox 作为一种新的布局技术,创建了一种新的布局规则,带来了 Flex Formatting Context,利用这类规则,我们可以轻松实现上面用 float 布局难以实现的效果。
下面是 Flexbox 的盒模型:
与 BFC 相比,FFC 复杂了许多。当对一个元素使用 display: flex
后,这个元素内部的布局模式就变成了 FFC,如上图所示。
通过使用与 Flex 相关的一些 CSS 新属性,我们可以轻松实现上面提到的难点:
-
怎么垂直居中一个元素?
给它的父元素设置:
.container { display: flex; justify-content: center; align-items: center; }
-
怎么让一个容器的子元素都占据相同的宽度?
给子元素设置
flex
:.item { flex: 1; }
-
怎么在不设置具体高度的情况下让同一行的子元素拥有相同的高度?
Flexbox 天然具有这个功能。
更多关于 Flexbox 的资料 MDN
8. Grid
Flexbox 已经足够优秀了,但是与 Grid 相比还是稍微逊色一筹,因为 Flexbox 本质上还是属于一维布局模型,只能在横向或竖向上进行布局,不能同时在横向和竖向上进行布局。
对于一些比较复杂的布局,如果要使用 Flex 进行布局的话,则得进行多层 Flex 嵌套。
而 Grid 则带着一种新的二维布局模式强势登场:
我们可以把 grid 看成是加强的 CSS 版的 Table,使用 gird 可以轻松地实现很多复杂的布局。
9. 布局兼容性方案
在写一个新的项目时,为了尽可能使绝大多数用户都有一个好的访问体验,我们应该考虑兼容性。
有的用户使用的浏览器版本比较旧,这些浏览器不支持一些新的布局技术,在这些浏览器上我们应该提供一版向下兼容的方案。
兼容方案的实现依赖以下几个方面的知识:
-
浏览器会跳过它不认识的CSS属性
-
CSS 规范对于:当一个元素应用了不同的布局规则后,哪种规则的优先级更高 有详细的规则。
-
Feature Query:可以使用 CSS 这个功能探测浏览器是否支持某个CSS属性
@supports (display: grid) { // 具体的 CSS 代码 }
举个不太严谨的例子,我们可以将 grid 布局向下兼容到 float 布局:
<div class="wrapper">
<div class="item">Item One</div>
<div class="item">Item Two</div>
<div class="item">Item Three</div>
</div>
* {box-sizing: border-box;}
.wrapper {
background-color: rgb(79,185,227);
padding: 10px;
max-width: 400px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.item {
// 当 grid 不被浏览器支持时,float将被启动
float: left;
border-radius: 5px;
background-color: rgb(207,232,220);
padding: 1em;
}
在上面的代码中,如果浏览器不支持 grid,那么 float 的布局规则就会激活,从而可以兼容哪些不支持 gird 的浏览器。
当然现实情况远比这复杂,作者对于布局兼容性这方面的了解还不够深刻,不敢过多妄言。
所以我仅在这抛砖引玉,希望能激起读者对于布局兼容性这方面的一些兴趣。
本文后面提供了一些关于布局兼容性的阅读材料,可以给想了解这方面的读者一些小小的帮助。