深度剖析Margin塌陷,BFC,Containing Block之间的关系

深度剖析Margin塌陷,BFC,Containing Block之间的关系

作者:殷荣桧

参考资料中是我从网上看到的有关塌陷,BFC中写的很好的几篇。讲的也很全面,教你如何用BFC,Containing Block(包含块解决)margin塌陷的问题。但是有一点他们都没有提及,就是为什么会有这些东西出现。

一、问题一:为什么会有Margin塌陷,是CSS设计时没有考虑到的Bug,还是设计者有意为之?为什么marign塌陷时,水平方向的不会发生塌陷呢?

二、问题二:CSS1.0中没有BFC,Containing Block.为什么要在CSS2.0中加入这两个特性?

三、问题三:CSS1.0中没有BFC,Containing Block,又是如何解决Margin塌陷的问题的?

四、问题四:.BFC除了解决Margin塌陷,还有别的作用吗?

问题五:BFC是如何形成的?

以上的这些问题,如果不去了解设计者的思路,是不太容易理清这些的!理清这些有什么用呢?目的只有一个:让你更好的记住BFC以及Containing Block的用法。千万不能死记硬背,硬背着overflow:hidden能解决margin塌陷。下次换个环境你依然蒙圈,因为你不懂原理,不知道为什么!接下来我把我这些天的学习总结记录下,希望能对您有所帮助。

一、首先第一个问题:

1.为什么会有Margin塌陷,是CSS设计时没有考虑到的Bug,还是设计者有意为之?为什么marign塌陷时,水平方向的不会发生塌陷呢?

规范1.0和2.0中分别是这样说的:

CSS1.0
Two or more adjoining vertical margins (i.e., with no border, padding or content between them) are collapsed to use the maximum of the margin values. In most cases, after collapsing the vertical margins the result is visually more pleasing and closer to what the designer expects
CSS2.0
无(因为在css1.0中已经规定)

CSS1.0中的规定写的很明白,我们是故意这样设计的,因为这样在大多数情况下符合平面设计师的要求。这边不禁有多了一个疑问,你(CSS设计者)咋知道设计师喜欢这样呢。再说了,要合并也不要你默认就合并了啊,我自己写明就是了,你为什么要多此一举呢,比如我要垂直间距20px就写成下面这样:

<div style="background-color:tomato;width:100px;height:100px;margin-bottom:20px;">
hello world
</div>
<div style="background-color:#bbb;width:100px;height:100px;margin-top:0px;">
hello world
</div>

我要垂直间距40px就写成下面这样:

<div style="background-color:tomato;width:100px;height:100px;margin-bottom:20px;">
hello world
</div>
<div style="background-color:#bbb;width:100px;height:100px;margin-top:20px;">
hello world
</div>

你偏要帮我把间距(原本20px+20px=40px)应改成20px,还说这样更符合设计师的要求.好像有点说不过去啊,很多人踩过这样的坑(你规范中说好的margin-top就是距离上边界的距离,margin-bottom就是距离下边界的距离,两个一起用的时候谁知道你就不按套路出牌了)。为什么CSS的制定者们要制定这样一个坑呢,还找了个符合设计师设计的理由呢?一开始我也蒙了,然后我就去stackoverflow上问Why the css specification...。一个老程序员给了我答案:

There's a historic reason. Mostly about how P(Paragraph) elements were rendered in the days before CSS. CSS needed to replicate the existing behaviour. But the behaviour still makes sense today for the reasons given in the question

原来是为了兼容在发明CSS1.0之前使用的P标签

在CSS之前的P标签(上下都有相同的边距)就是如下图所示:

也就是P标签天生就长这样,也没有什么css来让你改他的默认样式。再来看看现在的P标签(其实就是用css修饰的一个display:block;元素)长什么样

引自W3schools
p {
    display: block;
    margin-top: 1em;
    margin-bottom: 1em;
    margin-left: 0;
    margin-right: 0;
}

P标签只是一个壳,重点是要看里面的CSS样式,看见没有,margin-top:1em;margin-bottom:1em;如果没有margin塌陷,他长这样:

中间的间距就比开始个结束的要大,显然不好看(这就是css规范说的更符合设计师的设计),同时,显然也与老的P标签表现的不一致。那怎么办呢?这时CSS的制定者们想了一个主意,把这种情况作为一种特殊情况,另行规定,于是就诞生了margin collapse的官方规定(多规定的其他几项也是为更好的符合平面设计的视觉效果):

首先是一个大前提:元素之间没有被非空内容、padding、border 或 clear 分隔开。然后有符合下面几种毗邻情况:
top margin of a box and top margin of its first in-flow child
一个元素的 margin-top 和它的第一个子元素的 margin-top
bottom margin of box and top margin of its next in-flow following sibling
普通流中一个元素的 margtin-bottom 和它的紧邻的兄弟元素的的 margin-top
bottom margin of a last in-flow child and bottom margin of its parent if the parent has ‘auto’ computed height
一个元素( height 为 auto )的 margin-bottom 和它的最后一个子元素的margin-bottom
top and bottom margins of a box that does not establish a new block formatting context and that has zero computed ‘min-height’, zero or ‘auto’ computed ‘height’, and no in-flow children
一个没有创建 BFC、没有子元素、height 为0的元素自身的 margin-top 和 margin-bottom

这就是margin collapse的由来,所以到这个时候你应该对margin collapse有的彻底的了解了。所有的解决margin collapse无非都是破坏了上述的margin collapse成立条件之一。

关于margin collapse,你也可以参考这篇文章w3cplus.com/css/underst

二、接下来第二个问题:

2.CSS1.0中没有BFC,Containing Block.为什么要在CSS2.0中加入这两个特性?

在解决这个问题之前:要了解CSS1.0中并没有Position(static,absolute,relative,fixed)这个属性,相当于都是static属性。要知道,之前都是一个大的矩形Box套一个小的Box,每个box的都很安分,一个套一个,并没有absolute,fixed这样不受矩形Box model约束的怪物来扰乱Box的排列。

扰乱了之后带来了一个问题:

之前一个在CSS1.0中一个div,p这样的元素如果有margin-top这样的属性,并且值为30%时.

'margin-top'(css1.0中的定义)
Value: length | percentage | auto
Initial: 0
Applies to: all elements
Inherited: no
Percentage values: refer to width of the closest block-level ancestor (注意这边了)

这个30%根据定义就是相对于closest block-level ancestor,说白了就是相对于包住他的那个Box的宽度。

那在css2.0中的width如果是百分比又是相对于谁呢?CSS2.0上是这样写的:

'margin-top', 'margin-bottom'(CSS2.0中的定义)
Value: | inherit
Initial: 0
Applies to: all elements except elements with table display types other than table-caption, table and inline-table
Inherited: no
Percentages: refer to width of containing block (注意这边了)

可以看出CSS2.0中的width是相对于一个叫做containing block这样的一个东西的。现在是时候去了解他了,在了解他之前,就要了解为什么好端端的closest block-level ancestor不要了,改用containing block这个了。那是因为出现了Position:absolute | fixed这两个搅局者。接下来就分析一下,为什么搅局这的出现会带来containing block这个副产物。

(1)为什么css2.0中要增加position这样的一个属性。

设想现在你是一位前端工程师,用的是CSS1.0这套技术。产品要求你在线上已有的网站页面(比如如下的网页布局页面)添加一个永远定位在屏幕右下方的“返回顶部”的按钮。



没有position:fixed这样的属性给你用。那估计够你头疼的,因为你这些布局必然是在一个大的container 的div中,无论你在哪边加,都必然会对现有的布局造成挤压。 如下图:



无论你放在哪个div中,都将破坏原有的div结构,更致命的是,他不会定位在浏览器的视窗中,因为没法实现相对于浏览器视窗的定位,所以没法实现悬浮。所以,产品的这个简单的需求在CSS1.0时代变得非常难以实现。类似的很多相对于浏览器视窗的悬浮定位都无法实现。

所以制定CSS2.0时,制定者们就想怎么解决这个问题,于是就想到增加一个position的属性,也就是css1.0中的相当于都默认设置了position:static这样的一个属性,另外再增加position:fixed这样的一个属性。他的定位就是相对于浏览器视窗的。那这个时候如果用left:50%时,这个50%肯定时相对于浏览器视窗的。不能还是CSS1.0中的refer to width of the closest block-level ancestor因为closet block-level ancestor并不一定就是浏览器视窗。所以,就想以个名字来称呼这个浏览器视窗,就叫Containing Block吧。Containing Block就这样诞生了。

相对于浏览器视窗的定位解决了。此时,还有一个问题困扰了CSS1.0时代的工程是很久了,比如,产品提出了这样的一个需求:要求在一个设置了overflow:hidden的div中,也放一个“返回顶部”按钮。如下图所示:


这个怎么解决呢?CSS1.0功能又捉襟见肘了。position:fixed也无法实现,那就再加属性值position:absolute;并且让他不仅仅是相对于ancestor box,你可以随便指定他相对于谁定位,比如A元素。(只要设置A的position为非static就可以了)。那此时的这个A又怎么称呼呢?还叫containing block吧!

此时,containing block的概念逐渐清晰,我们再看一下CSS2.0对于containing boxd的定义:

The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element. The containing block of an element is defined as follows:
1.The containing block in which the root element lives is a rectangle called the initial containing block. For continuous media, it has the dimensions of the viewport and is anchored at the canvas origin; it is the page area for paged media. The 'direction' property of the initial containing block is the same as for the root element.
2.For other elements, if the element's position is 'relative' or 'static', the containing block is formed by the content edge of the nearest block container ancestor box.
3.If the element has 'position: fixed', the containing block is established by the viewport in the case of continuous media or the page area in the case of paged media.
4.If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
1.In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined.

2.Otherwise, the containing block is formed by the padding edge of the ancestor.
If there is no such ancestor, the containing block is the initial containing block.

怎么样,是不是和我们推断的一样。这就是Containing Block的诞生记。

知道了有Containing Block的原因之后,我们再来看一下,为什么又会诞生BFC(Block Format Context)这样的东西。

前面,我们提到CSS1.0,CSS2.0中都有默认的margin collapse,那我如果我们就是不想要这个塌陷呢!这个是后CSS的制定者们又想,能不能制定一个属性,让上下的两个div隔离开来,就像孙悟空给唐僧用金箍棒画的圈一样,外界的妖魔鬼怪都无法进入,这样让他们的Margin不会塌陷合并。制定者们给金箍棒画的这个圈取了个名字叫BFC。他们还制定了触发BFC的条件,这要符合这些条件中的一个,就能请来孙悟空画上隔离区。符合触发BFC的条件如下:

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

有了BFC就可已很好的解决margin collapse.这里我就不细说了,你可以参考这篇文章:css中的BFC

3.CSS1.0中没有BFC,Containing Block,又是如何解决Margin塌陷的问题的? 这个在第一个问题中提及到了,只要破坏margin collapse成立的任何一个条件就可以了。

4.BFC除了解决Margin塌陷,还有别的作用吗?

就和爱迪生一开始发明灯泡只是为了照明,但后来被人们用到KTV渲染气氛了一样。BFC也一样,发明他的时候是为了隔离,因为隔离诞生了很多作用,还是参考这样的一篇文章css中的BFC

5.BFC是如何形成的?

1.根元素(整个页面就是一个大的BFC);

2.float为 left | right;

3.overflow为 hidden | auto | scroll;

4.display为 inline-block | table-cell | table-caption | flex | inline-flex;

5.position为 absolute | fixed;


总结:至此你知道了为什么在CSS中有Margin collapse,BFC,Containing Block.这些事物,相信这样一定能比你硬记住这记住怎么用会更牢靠。



参考资料:
理解CSS中的BFC(块级可视化上下文)[译] css中的BFC 深入理解BFC和Margin Collapse CSS 中 block-level boxes、containing block、block formatting context 三者之间的区别和联系是怎样的? CSS1.0官方规范 CSS2.0官方规范
编辑于 2017-12-05