云陪读』 - 《精通CSS》第3章 可见格式化模型

358 阅读16分钟

本书《精通 CSS》之前的章节:


第 3 章仍是本书的基础章节。本章将会给大家介绍盒模型相关的概念、几种常见的可见格式化模型(包括定位、浮动、格式化上下文)以及新型布局模块,其中较新的布局方式将在后续章节详细介绍。

本章文中示例代码托管在CodeSandbox

3.1 盒模型

盒模型是 CSS 的核心概念,描述了元素如何显示以及(在一定程度上)如何相互作用、相互影响。

页面中所有的元素都被看作一个矩形盒子,这个盒子包含元素的内容、内边距、边框和外边距

如下图所示:

盒模型示意图
盒模型示意图

内容区是元素内包含的内容所在区域。

内边距(padding)是内容区周围的空间,给元素应用的背景会作用于元素的内容、内边距和边框(默认值到边框,但实线边框看不出来,虚线可看出,可以通过background-clip属性修改这一行为)。

边框(border)会在内边距外侧增加一条线框,可以是实线、虚线或点划线

外边框(margin)在边框的外侧,是围绕在盒子可见部分之外的透明区域,用于控制元素之间的距离。

此外,还有一个与边框类似的属性,即轮廓线(outline),这个属性会在边框盒子外围画出一条线,但这个线不会影响盒子的布局,即不会影响盒子的宽高

如下,我们在模拟的边框上加一个虚线的轮廓,其只是在外围包括了一层轮廓,并不影响布局。

轮廓不影响布局
轮廓不影响布局

最后,内边距、边框和外边距都不是必需的,因此它们的默认值都是 0。不过,浏览器默认的样式往往会给很多元素添加外边距和内边距,但不同浏览器添加的样式并不统一。这时可以用我们第二章提到的样式重置,推荐大家使用 Eric Meyer 的CSS ResetNicolas Gallagher 的 Normalize.css

3.1.1 行盒子、块盒子和匿名盒子

像大家常见的pharticle这些元素都是块级元素,它们显示为块级盒子(简称块盒子,block box)。

相应的,spanstrong这些是行内元素,所以它们会以行内盒子(简称行盒子,inline box)显示在行内。

CSS 中有几种不同的定位模型,包括浮动、绝对定位和相对定位。除非特别指定,否则所有的元素盒子都在常规文档流中生成。这时,元素盒子的位置,由在 HTML 中的位置决定。

块级盒子会沿垂直方向堆叠,盒子在垂直方向上的间距由他们的上、下外边距决定。

行内盒子是沿文本流水平排列的,也会随文本换行而换行。它们之间的水平间距可以通过水平方向的内边距、边框和外边距来调节。但是行内盒子的高度不受其垂直方向上的内边距、边框和外边距的影响且给行内盒子显示地设置宽高也不会起作用

修改行内盒子高度的唯一方式是修改行高line-height

当然也可以通过display改变元素的显示,改为块级格式,这是上下边距、宽高将会起作用。

最后还有一种很特殊的盒子。多数盒子都是基于明确定义的元素生成的。不过有一种情况,就算不明确定义元素,也会生成块级盒子。如下面这样,在section这个块级盒子的开头加入some text,即使没有声明这段文本是块级元素,也会当成块级元素。

<section>
  some text
  <p> some more text</p>
</section>

上面这种情况下,some text会生成一个匿名的块盒子

同样,也有对应的匿名行盒子。如下代码所示,before textafter text都会生成匿名的行盒子。

<p>before text<span>some text</span>after text</p>

3.1.2 盒子大小

默认情况下,元素盒子的widthheight指的是内容区的宽高,即元素可渲染内容区的宽度和高度。这时候添加边框和内边距并不会影响内容盒子的大小,而是会导致整个盒子变大。

但对于我们样式编写者来说,更加倾向的是元素盒子的大小可以通过widthheight指定,在新增边框和内边距的时候,内容区自动计算,而不是每次都要手动的调整,才能保证元素盒子的整体宽高不变。

这样的计算方式也符合现实中的包装箱模型。对于包装箱,箱子的四壁就是边框,从视觉上决定了箱子的大小;内边距就是箱子内部的填充层,用于保护装在箱子内的物品。在箱子尺寸(长宽高)固定的情况下,箱子四壁的厚度和填充层的厚度会压缩箱子内部用于放物品的空间。在堆放箱子时,不管箱子之间的间距(类似外边距)是多少,都不会影响箱子的可视大小。

如上,盒模型的计算如果以这一实物方案为参考,更加容易被人理解。所以,浏览器开发人员使用开始提到的计算方式是很不明智的。

题外话,早期浏览器开发的大佬们都会有失误,那么日常工作中的我们有何惧犯错呢,勇敢去做就是了。

值的庆幸的是,后来者们对于这一问题进行了完善,我们可以通过box-sizing属性来覆盖默认的盒子大小计算行为。

box-sizing的默认值时content-box,对应着盒模型大小的默认计算行为,会把宽度值应用给内容区。box-sizing还有另一个值border-boxwidthheight的计算会包括内边距和边框。外边距一直不会算到宽高内,只会影响盒子在页面中占据的整体空间。

最后,内边距、边框和外边框可以应用于元素的四边,也可以单独用于某一边。外边距甚至可以使用负值,从而使得元素在页面中移动。

内外边距的值可以说任意的长度单位,但是当使用百分比时,有一点需要记住,四个方位的内、外边距都是基于包含块的宽度来计算的

划重点,这个点时很常见的面试点哟。

包含块并不一定就是父元素,后面我们会介绍。

3.1.3 外边距折叠

前面,我们认识了各种盒子以及如何计算盒子的大小。

其中外边距只会影响元素与元素之间的距离,是一个比较简单的概念。但是它也有个会让人困惑的机制,叫做外边距折叠。所谓外边距折叠,即垂直方向上的两个外边距相遇时,会折叠成一个外边距,折叠后外边距的高度等于两者中较大的那个高度

外边距的折叠有以下几种情况(很重要!)。

  1. 当两个元素垂直堆叠时,上方元素的下边距会与下方元素的上边距发生折叠。
  2. 对于嵌套的父子元素(假设只有一个子元素),如果父元素没有内边距和边框,那么它们的上下边距均会发生折叠。
  3. 甚至同一个元素的外边距也会折叠,如果存在一个空元素,只有外边距没有边框和内边距,此时自身的上下边距接触,发生折叠。
  4. 折叠后的外边距又接触其他元素的外边距,还会继续折叠。

大家可能会困惑,为啥会有外边距折叠,就正常的显示不就好了。这也是有实际参考的。试想,我们有一篇文章,包含多个段落,我们给每个段落指定了上下边距(假设 20px),如果没有外边距折叠,那么相邻的两个段落之间的间距就会是 40px,而第一个段落的上边距和最后一个段落的下边距只有 20px,这就导致中间是两头的两倍,很不美观。而有了外边距折叠就不会这样了。所以外边距折叠是为了排版而生的。

最后,外边距折叠只会发生在常规文档流中块级盒子的垂直方向上。行内盒子、浮动盒子或绝对定位盒子的外边距都不会折叠

3.2 可见格式化模型

3.2.1 包含块与定位

常规情况下,包含块是元素的最近块级父元素,但这并不是一定的。

只有当元素的定位方式是静态定位或相对定位时,这时候元素都还在正常的文档流中,其包含块是最近的块级父元素。也就是说这个父元素的display属性值必须提供类似块级的上下文,如block/inline-block/table-cell/list-item等。

当元素的定位模型改为absolutefixed时,包含块的规则就会发生变化,下面我们来一次讨论各个定位模型。

3.2.1.1 相对定位

相对定位是将position属性的值设为relative,这时元素还会在文档流中占据原有的位置。我们可以通过toprightbottomleft设置四个方向的偏移值,如top: 20px;向下偏移 20px,不过文档流中占据的位置不变

无论是否位移,相对定位的元素仍然会在原始文档流中占据初始的空间。因此,平移后会遮挡其他元素。至于为什么会遮挡其他元素,这涉及到层叠相关的知识,推荐大家阅读笔者之前写的一篇文章CSS 的“层”峦“叠”翠

3.2.1.2 绝对定位

绝对定位会把元素拿出文档流,不会再占用原来的空间,文档流中的其他元素会各自重新定位,仿佛绝对定位的元素不存在一样。。

绝对定位元素的包含块是距离他最近的定位祖先,也就是position值为static之外任意祖先元素。

如果找不到这样一个定位祖先,那么它就是相对于文档的根元素进行定位的,文档的根元素也叫做起始包含块

确定了包含块之后,我们也可以用toprightbottomleft来设置元素相对于其包含块的位置。

3.2.1.3 固定定位

固定定位是由绝对定位衍生出来的,不过其包含块在设计之初是视口(viewport)。

只所以说设计之初是视口,是因为在后来引入了 transform 之后,当元素设置了 transfrom 属性后,会创建一个包含块,并且这个包含块会影响固定定位的子孙元素。也就是固定定位的子孙元素会相对于这个包含块定位。

固定定位通常用于让导航区始终可见,如固定侧边栏、固定顶栏等。

3.2.2 浮动

上面我们介绍了定位这一可见格式化模型。下面我们来看看另一种,浮动模型。

浮动盒子可以向左或向右移动,直到其外边沿碰到包含块的外边沿,或者碰到另一个浮动盒子的外边缘

浮动盒子也会脱离常规文档流,所以常规流中的其他块级盒子几乎会当浮动盒子不存在。

之所以说“几乎”,是因为浮动元素会影响其后常规文档流中块级盒子的文本内容。文本内容会记住浮动元素的大小,并在排布时避开它,形成文字包围的效果,如下图。

浮动-文字环绕
浮动-文字环绕

浮动就是为了在网页上实现文本环绕图片的效果而引入的一种布局模型。所以比较推荐大家只有在这一用途的时候使用。

当一个块级盒子变为浮动盒子时,其宽度会自动收缩到适应内容大小的宽度,除非显示指定其他宽度。

如下例所示,原本各盒子是撑满的,当盒子 1 有浮动后,内容变成了自适应。且因为盒子 1 脱离了正常文档流,覆盖在了盒子 2 上面。

浮动-宽度自适应.png
浮动-宽度自适应.png

如果三个盒子都向右浮动,则后面两个会向右移动直到碰到自己前面的浮动盒子。如下:

全部右浮动
全部右浮动

大家应该看得出来,当所有元素全部右浮动时,三个盒子超出了父元素的范围(绿色框),这就是常说的元素塌陷。至于该怎么解决元素塌陷的问题,下面我们再说。

如果包含块太窄无法容纳所有浮动元素水平排列,后面的元素会向下移动如下图左侧。如果浮动元素高度不同,后面的浮动元素在向下移动时可能会“卡”在前面的浮动元素左侧,如下图右侧。

浮动折行
浮动折行

浮动元素在浮动时不仅会受到同级浮动元素的影响,还会受非同级元素的影响。如下图所示,第二组浮动盒子会受到第一组盒子的影响。

浮动受非同级元素影响
浮动受非同级元素影响

如上,这种情况下,第二组合子挂在了第一组盒子的左边。如果我们不想让第二组的盒子 1 挂在第一组的盒子 3 左边应该如何处理呢?

很简单,我们可以通过clear属性的leftrightbothnone来指定盒子的哪一侧不应该紧挨着浮动盒子。具体的清除效果,大家可以自己试一下。

清除浮动时,浏览器会在这个元素的上方添加足够的外边距,从而将元素的上边缘垂直向下推移到浮动元素下方。如下图所示。所以,如果想给“已清除浮动的”元素添加外边距,除非添加的值超过自动添加的值,否则看不出有效果

文字环绕效果-清除浮动后
文字环绕效果-清除浮动后

前面我们提到了元素塌陷,这个也可以通过clear的这一特性来解决。可以在发生塌陷的父元素内添加一个空的元素,并清除浮动,这时空的元素前面会留下足够的空间来容纳浮动元素。

clear解决元素塌陷问题
clear解决元素塌陷问题

当然,这样做会有一个无用的空元素,所以我们可以通过::after伪元素来替代这一空元素。这样会简化 HTML 标签。

3.2.3 格式化上下文

CSS 中有一个很重要的概念,叫做格式化上下文(formatting context)。其中行级格式化上下文前面略有提及,如垂直外边距对于行内盒子无效。大家所经常提及的是块级格式化上下文(block formatting context,简称 BFC)

块级格式化上下文规定页面必须自动包含浮动的元素,且所有块级元素的左边界默认与包含块的左边界对齐。

我们可以通过以下规则允许元素创建自己的格式化上下文:

  • display 设置为inline-block/table-cell的元素;
  • float 属性值不是none的元素;
  • 绝对定位的元素;
  • overflow 属性值不是visible的属性。

但是当某个块级元素本身也触发了块级上下文时,且挨着一个浮动元素时,它就会忽略边界必须接触包含块边界的规则。此时,这个元素会收缩到适当大小,紧跟在浮动元素之后。

如果想实现一个左右分栏的布局形式,我们可能会用float进行如下实现。

代码如下:

<style>
  .wrapper {
    width: 90vw;
    height: 40vh;
    border: 2px dashed #ddd;
  }

.wrapper:after { display: block; content: ""; clear: both; }

.left { float: left; width: 25%; height: 100%; background-color: lightgreen; }

.right { float: right; width: 75%; height: 100%; background-color: lightblue; } </style>

<!-- html --> <div class="wrapper"> <div class="left">侧边栏</div> <div class="right">主区域</div> </div>

效果图如下:

左右分栏-float实现
左右分栏-float实现

从代码可以看出,我们要分别给左右浮动块指定宽度,并且要通过伪元素来清除浮动,从而保证不发生元素塌陷。

不过如果利用块级格式化上下文,我们可以更简单地实现上面这一效果,将 CSS 进行如下修改。

<style>
  .wrapper {
    width: 90vw;
    height: 40vh;
    border: 2px dashed #ddd;
  }

.wrapper, .right { overflow: auto; }

.left { float: left; width: 25%; height: 100%; background-color: lightgreen; }

.right { background-color: lightblue; height: 100%; } </style>

可以发现,我们给包裹元素和右侧元素加上了overflow: auto;使其触发了块级格式化上下文。对于外层的包裹元素,块级格式化上下文会使其自动包含内部的浮动元素,从而省去了清除浮动的相关代码。对于右侧元素,块级格式化上下文省去了宽度的指定,其会自动收缩大小,并紧挨浮动元素。

3.3 其他布局模块

除了上面介绍的定位、浮动等,CSS 还有一些比较新的更加灵活稳健的 CSS 布局模块。如弹性盒子布局、网格布局、多栏布局、Region 后续章节会进行详细介绍。

其中,Region 是为了实现不同元素间的灌文接排。可以把一个元素作为内容来源,但它不在常规文档流中,其内容可以灌排到页面中的其他元素内。这是为了实现一些印刷品的排版样式。但是因为还没有浏览器有兴趣实现,本书不做介绍。感兴趣的可以看下这篇文章A Beginner’s Guide to CSS Regions


你多久没有读完一本技术书了? 你是否列了长长的待读书单,却迟迟没有行动? 你是否每逢购物节,就血拼了一堆回来? 又或者你觉得一个人读书太过枯燥?

如果你是,那从现在跟歪马一起,歪马陪你一起云读书。 歪马会选一本技术类书籍,每 2~5 天学习并总结一个章节供大家一起围观,欢迎大家监督共进,后期大家可以推荐选材,内容局限于前端周边 🤓。


如果你喜欢,欢迎扫码关注我的公众号,我会定期陪读,并分享一些其他的前端知识。