Box position in CSS

本文的知识主要来源于 learn CSS Layout, 本篇文章是对书中第一章相关内容的梳理,总结。

一个元素的横竖位置由定位方式(positioning scheme)所决定,而定位方式主要由三个属性display, float, position影响。display属性的默认值是block或者inline,这个属性决定了布局时候使用的算法。position属性的默认值是static,这个属性决定了控制元素定位的方式。float属性的默认值是none,这个属性允许其他元素浮动在这个元素的周围。

理解 float 和 absolute positioning 的原理,可以通过它们与 normal flow 的相互影响来理解。所以我们从 normal flow 开始剖析。

理解定位,布局主要考虑的是以下两点

  • 这个元素的大小(高度和宽度),这个元素的水平校准方式和垂直校准(align)方式
  • 同一个父元素的元素们相互之间相对摆放的位置。
    换而言之,如果每个元素的上面两点给定了,是否整个布局就确定了呢?

同一个父元素的元素之间相对摆放位置是由这个父元素为子元素建立的格式上下文(format context)决定的。
normal flow中的盒子属于一个块或者行内的格式上下文,块级盒子加入块级格式上下文,行内盒子加入行内格式上下文。

父元素在建立格式上下文时考虑子元素是块级的或者是行内级的。

块级元素的含义: 每一个块级元素会生成一个块级盒子用来包裹子孙元素,同时也是这个盒子参与定位计划。每一个块级盒子都是一个块级包含盒,可以包含其他的盒子而且有一个特定的格式上下文。

行内元素的含义: 行内元素不产生新的块,并且内容可以分开成多行。

两种格式上下文(BFC,inline format context)可以被简单的认为是水平堆叠区,或者是垂直堆叠区。这一点在后面解释,先注意另外一点。

所有盒子要么是块格式上下文,要么是行内格式上下文,也就是一个父元素的所有子元素布局的时候只有一种格式上下文。那当行内元素和块元素混合的时候怎么处理呢?
会产生一种叫匿名盒的东西,这个匿名盒参与父元素的格式上下文的组成,自己本身为子元素提供了一个格式上下文。它本身不存在,存在的是它的子元素以及它的父元素。

匿名盒

匿名盒的诞生是为了解决 块元素和行内元素混合,以及有一些不被标签包含的文字所带来的格式上下文 的问题。

匿名盒的介绍不过多叙述可以参考Anonymous block boxes.

有几个比较重要的规则:

  1. 如果一个块级包含盒有一个块级盒子在里面,那么强迫它的所有子元素都是块级盒子(通过产生匿名盒)
  2. 如果一个行内盒子包含一个块级盒子,那么这个行内盒子在块级盒子的地方折断,分成两个部分,每个部分变成块级的盒子。
    简单的理解就是块级盒子和行内盒子混合的时候,行内盒子会被匿名块盒包裹起来,变身成为块级盒子。

normal flow: block formatting

对于块级格式上下文,所有盒子都是纵向的一个一个排列,即使宽度不足以充满父元素的宽度。每一个盒子的左边界靠着父元素的左边界。

1
2
3
4
5
6
7
8
9
10
11
12
.float
float: left;
}
.foo {
padding-top: 10px;
}

.bar {
width: 30%;
}

.baz {
width: 40%;
}

1
2
3
4
5
6
<div class="container violet">
<div class="float red">float</div>
<div class="foo blue">foo</div>
<div class="bar green">bar</div>
<div class="baz orange">baz</div>
</div>

懒得画例子,同时也就省了说明, copy一下

  • every block box is on the left outer edge of the containing block
  • the presence of a float does not influence the position of the left outer edge (per spec) in any way, except to offset the text
  • the block box foo (which has no width set) expands to the full container width
  • the two other boxes, which have set widths extend from the left edge of the container
  • the two other boxes are not moved around in any way, even though they would for example fit on a single row

总结一下,如果某个父元素有一个子元素是块级的,那么这个父元素为它的直系后代生成的生活环境(格式上下文)就是块级的,那么不是块级的元素呢一个个假装自己是块级的(通过匿名盒),然后父亲看着他们一个一个纵向排列,觉得非常满意。块级的元素非常厉害,独占一行,并且能为后代创建一个生活环境。

Normal flow: inline formatting

行内格式上下文比较麻烦,因为行内元素是会分成多行的,也就是说行内格式上下文会分成多个line boxes,这个东西在html中是不体现出来,但是内在布局中又有很大的影响。

line boxes也可以理解成一个匿名的盒子,它是独占一行,并且往往宽度就是整行的宽度,高度由这一行包含的所有盒子决定。

当一个行内元素的宽度超过它的包含块的宽度,它就会断成多行生成line box,如果是一个替换元素不能断则纵向溢出这个line box。

介绍控制对齐发式的两个属性: text-align和vertical-align。

text-align用于包含块,决定包含块内的行内元素的水平对齐方式,有left,right, center justify等等属性。

先说说行内元素的另外一个问题: 两个行内元素,在HTMl的标签中如果中间有换行或者空格,那么显示的效果是这两个元素是有间隙的,不像块元素从上往下,元素是没有间隙。

解决这个问题的办法有

  1. 将标签写在一起例如

    1
    </div><div>

    这样。

  2. 设置font-size为0,然后额外为每一个元素设置他们所需要的font-size。
  3. 设置white-space:nowrap,这一属性不能避免间隙但是能让元素不换行,右边的元素会overflow。
  4. 使用CSS3的text-space-collapse属性。使用之前请确认浏览器的支持性

vertical-align

vertical-align比text-align要更难理解,当然也更难使用。这一属性控制盒子的垂直对齐方式,只对行内盒子有效。在word中有上标,下标,这一属性就是控制这个东西的。

不难想到对于垂直对齐来说,高度是很重要的。行内元素的高度,设计到纵向的padding,margin是不能影响到高度的。对了这里说的高度是什么高度呢。

如果是包含块的高度那不是设置一下包含块的height不就完事了嘛?这里说的是line box的高度。这个高度由line-height属性决定。

相信大家都使用过height和line-height设置成一样使得文字垂直居中的方案。

line-height在没有设定值的时候是和字体高度有关,与父元素的高度没有任何的关系。line-height的取值有多种,可以参考css行高line-height的一些深入理解及应用
当然去阅读本文参考的那篇英文文献是最好的选择,张鑫旭的文章往往不解释本质的原理,更多的是引导你去理解这个问题。

了解了行高之后,接下来理解vertical-align的值:

baseline: 将盒子的基线与父盒子的基线对齐,如果盒子没有基线,将bottom margin edge与父盒子的基线对齐

middle: 将盒子的中点 与父盒子基线加上父元素x字母高度一般的位置对齐

sub: 下标对齐

super: 上标对齐

text-top: 盒子的顶部与父内容区域的顶部对齐

text-bottom: 盒子的底部与父内容区域的底部对齐

百分比: 升高或下降行高乘以百分比距离

长度: 升高或下降这一长度

top和bottom与以上不一样,这两个值是与line box的top或者bottom对齐。

注意,一个行内盒子(inline box)包含:

  • font-size,决定文字区域的大小
  • line-height, 决定inline box的高度
  • 一个baseline, 由font决定的位置

line box(不是inline box),高度由每一个它所包含的inline box的高度计算,然后应用vertical-align来对行内的元素们进行对齐。

每一个line box包含:

  • font-size,从父元素继承而来

  • 由行内盒子的高度和对齐方式确定的高度

  • 一条有“strut”定义的基线

Normal flow: relative positioning

这一元素在某些情况下非常有用,你希望整个布局不被破坏(relative的元素在normal flow中的位置不会改变),但是希望这个元素的位置能够发生移动。

也就是你希望它默默变化,不对你的布局产生破坏的情况下可以使用这一属性进行定位。方便好用无害。

Float positioning scheme

关于浮动,老生常谈的话题就是清除浮动,在谈这个话题之前,我们先看一个例子(到codepen跑一下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.blue{
border: 3px solid blue;
}

.red{
border: 3px solid red;
}

.green{
border: 3px solid green;
}

.orange{
border: 3px solid orange;
}


.float {
float: left;
height: 500px;
}

.para {
margin:0
}

1
2
3
4
5
6
<div class="para blue">Text inside a block-level box placed on a line
box before the float</div>
<div class="para green">Text before the float. <div class="float
red">
The float</div> Text after the float.</div>

<div class="para orange">Text inside a block-level box placed on a line
box after the float</div>

在这个例子中我们可以看到,The float浮动了,剩下的那些字浮动到了这个元素的周围,但是他们的border还是在原来的位置,没有发生变化。

float是影响line box的宽度,使得line box变窄了,但是实际的box没有发生变化。

记住: 同一格式上下文中建立新的块格式上下文的元素不能与浮动元素的margin box重叠。建立新的块格式的上下文方法就是设置overflow属性不为visible。

如果有必要甚至会在这个元素身上清除浮动迫使它跑到float元素的下方。更多的情况下是有足够的空间让元素调整宽度。可以试试在上例中为orange类添加overflow:auto。然后再增加width:100%属性。

Float clearing

接下来我们谈谈清除浮动。我们可以使用clear属性来清除浮动。

那么这个属性是什么意思呢。clear有4个取值,left, right, none, both。left:清除左浮动,拥有这一属性的元素它的上border必须位于拥有float:left属性的元素的下border的下面。

同理right:清除右浮动,拥有这一属性的元素它的上border必须位于float: right属性元素的下border的下面。both,就是两个浮动都清除。

利用这个原理清除浮动一般使用一个伪元素

1
2
3
4
5
.clearfix-pseudo:after {
content: "";
display: table;
clear: both;
}

可以这样去理解after,在元素的闭合标签前面加一个div元素,这个div元素就是这个after伪元素。

float元素从正常流中被移除,所以父元素计算高度的时候并不包含float元素的高度。想象一张情况,我们放一张图片,然后希望有好几段文字(多个p元素)能浮动在这个图片中间。

我们可以在这多个p元素中间一个放一个图片,然后图片浮动,如果父元素计算高度的时候包含这个图片,我们就不能实现文字环绕这个图片的效果了。

这也是float属性的初衷。

这带来的问题就是拥有float元素的父元素的高度坍塌,尤其是文字高度不够的情况下,父元素有border,浮动元素就溢出(overflow)了。

这样在大多数情况下都是不合理的。标准也意识到了这种不合理,他们制定了一个新的规则。

当建立新的BFC(block formatting context)的时候要将float元素的高度考虑进去。建立新的BFC是,设置float属性,设置overflow属性,设置display:block属性。

将float元素重新纳入监管体系。

所以也就有了使用这种方式来清除浮动,设置float属性和display属性副作用很大,设置overflow属性副作用也不小,所以考虑使用这种方式清除浮动,要三思。

Absolute / fixed positioning scheme

这个没什么好多说的,当你定位遇到了困难,ok,绝对定位帮你解决各种不服。 理论上说,所有布局你都可以用absolute的方式去做,就是比较麻烦。可维护性和可读性也不强。但是在局部解决居中,解决特殊需求的时候,绝对定位是你的利器。

(wan)