β

外边距合并规则

黯羽轻扬 4 阅读

写在前面

margin的合并规则算是 CSS盒模型里最复杂部分 ,没有之一。因为这部分内容涉及很多不太容易理解的概念,例如clearance(间隙)、normal flow/in-flow(常规流)、BFC(块格式化上下文)、line box(行框)、inline box(行内框)、bidi(双向环境)等等

CSS盒模型不只是7项水平属性 + 7项垂直属性:

margin
  border
    padding
      width/height

P.S.想起高跟鞋的梗——“不仅有padding,今天还加了margin”

相关的内容至少还包括:

盒模型是视觉格式化模型中的基础单元,是CSS布局模型中必不可少的一部分

CSS盒模型描述了一个为文档树中的元素生成的并根据视觉格式化模型进行布局的矩形框

(引自 8 盒模型

所以,盒模型也是CSS在文档树之上建立的第一层抽象,是CSS布局控制与文档元素直接关联的部分。而外边距合并是直接影响垂直格式化的因素之一,有必要深入理解

一.经典场景

下列例子中,假设UA没有默认样式表,未声明的样式属性都依照规范取其初始值

另外,假设UA都是遵守CSS规范的

1.列表项间的外边距合并

li {
    margin: 8px;
}

那么列表项之间的间距是多少?

.li-case1 li {
    margin: 8px;
    /* 添个上内边距 */
    padding-top: 1px;
}
.li-case2 li {
    margin: 8px;
    /* 添个下边框 */
    border-bottom: 1px solid;
}

在case1和case2中,列表项间距分别是多少?

2.深层嵌套的外边距合并

/* 缩进表示对应文档结的构嵌套关系 */
div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    min-width: 100px;
    min-height: 100px;
}

这4个嵌套的div渲染结果是什么样子?

div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    min-width: 100px;
    min-height: 100px;
    /* 添个border */
    border: 1px solid;
}

现在呢?

div.outer,
  div.container,
    div.content,
      div.inner {
    margin: 10px;
    /* 删掉min-width, min-height和border */
}

那么现在呢?

3.带间隙的外边距合并

div.container {
    border-top: 1px solid;
    background: #ccc;
    margin-bottom: 60px;
}
  /* 缩进表示对应文档结的构嵌套关系 */
  div.float {
      float: left;
      width: 100px;
      height: 50px;
  }
  div.following-float {
      clear: left;
      margin-top: 50px;
  }
div.following-container {
    color: red;
}

红色文本顶端距 .following-float 底端的距离是多少?

div.container {
    border-top: 1px solid;
    background: #ccc;
    margin-bottom: 60px;
}
  /* 缩进表示对应文档结的构嵌套关系 */
  div.float {
      float: left;
      width: 100px;
      height: 50px;
  }
  div.following-float {
      clear: left;
      /* 把50改成49 */
      margin-top: 49px;
  }
div.following-container {
    color: red;
}

现在呢?

再把 50 改成 0 51 呢?又分别会出现什么情况?

P.S.这些问题的答案此刻还是未知的,因为Demo还没开始写;-)那么就有了足够的时间容我们 认真猜一下

二.合并条件

什么样的外边距会发生合并?

水平外边距不合并。 相邻的垂直外边距会合并 ,除了2种特殊情况:

第1条跳过,对根元素应用外边距不在情理之中

第2条引入了一个新概念,叫“ 间隙 ”,英文名clearance,看样子与 clear 属性有关,实际符合直觉,是指 clear 属性导致元素位置移动形成的间隙,见CSS规范 9 视觉格式化模型 。隐含两个关键点:

如果满足这两个条件,就说一个元素 带有间隙

注意 :如果应用了clear属性,元素的实际位置不变,比如通过 margin-top 把元素放到那个位置的,此时元素自身的布局位置与 clear 效果位置一样(即 clear 属性没有带来额外的 空间占用 ,所谓的间隙),就不具有间隙。反过来,如果应用 clear 属性,导致元素的实际位置发生了变化,即元素上方有一部分空间是 clear 属性带来的,那么就算带有间隙

带有间隙还不够,还要该元素的上下外边距相邻(意味着元素的实际高度为0,且没有 padding, border ),同时满足的话,这个元素的外边距合并会受到限制:其外边距只和紧挨着的兄弟的相邻外边距合并,合并后的结果不会再和父级块的下外边距发生合并

P.S.到这里有挑战经典场景3的入场券了,但还差得很远

“相邻”的定义

两个外边距在什么情况才算“相邻”?

3句话4个新概念,深度优先过一下

流内

流内/流外(in-flow/out-of-flow)是指是否用常规流定位方案来布局该元素

继续深度优先,定位方案分3种:

元素既没有浮动( float 属性的应用值为 none ),也没有绝对定位( position 属性的应用值不为 absolute ),并且不是根元素,那就按常规流来布局,就属于 流内元素 ,否则就是 流外元素

块格式化上下文

浮动,绝对定位的元素,非块盒的块容器(例如inline-blocks,table-cells和table-captions),以及’overflow’不为’visible’的块盒(当该值已被传播到视口时除外)会为其内容建立新的块格式化上下文

在一个块格式化上下文中,盒在竖直方向一个接一个地放置,从包含块的顶部开始。两个兄弟盒之间的垂直距离由’margin’属性决定

也就是说,如果没人建立新的BFC,那么就处于当前BFC。像JS作用域一样,默认大家都位于最外层作用域(最外层块格式化上下文),遇到普通块级盒就放进块格式化上下文,遇到特殊的(浮动,绝对定位的等等)就新建一层作用域(建立新的块格式化上下文),它里面的元素都放进这个内层作用域(新的块格式化上下文)

布局完成后从格式化上下文的角度来看,就是一系列嵌套的BFC,每个BFC负责管理一组块盒(或者说块级元素)的布局

注意:这里不提行内格式化上下文,因为区分出不同的行内格式化上下文没有太大意义(规范定义中,没有关于跨行内格式化上下文的特殊场景)。那么, 什么时候会创建新的行内格式化上下文? ,根据规范,只在块容器只含有行内级盒时才创建一个新的行内格式化上下文,不像BFC可以显式地强制创建

P.S.关于何时会创建新行内格式化上下文的更多讨论,请查看 When does a box establish an inline formatting context?

行框

包含来自同一行的盒的矩形区域叫做行框

一个行框总是足够高,能够容纳它包含的所有盒。

行框是CSS对行的抽象表示,每行元素都处于同一个行框里。如果太长放不下出现自动换行,那么就会为下一行再创建一个行框。另一方面,行框不是纯粹的抽象定义,它具有宽度和高度,用于决定行布局

相邻外边距之间“没有行框”可以简单理解为没有行内元素把它们隔开

垂直相邻框边界

下列4种场景满足外边距都属于垂直相邻框边界的情况:

看起来太长,我们简化条件,假设都是流内元素的话,那么:

P.S.这里的“真空”是指——把薯片抽成真空。要么里面什么都没有,要么流内孩子都被抽离了

也就是说,“相邻外边距”的位置定义具体分3种情况:父子,兄弟和 自身 (自身上下外边距合并是比较奇特的)

重新理解“相邻”与外边距合并

有了前面的概念铺垫,现在我们把零散的点整合起来,先重新定义“相邻”:

父子,兄弟或元素自身的外边距紧挨在一起 就是“相邻”

还有一个关键点: 紧挨 。就是说这两个外边距没被“墙”隔开,“墙”分3种:

到这里,“相邻”已经很清楚了,我们再反推外边距合并的定义:

非根元素的相邻垂直外边距会合并,带有间隙的话,合并受限

受限 是指带有间隙元素自身上下边距相邻的话,只能与兄弟元素的外边距合并,无法和父元素的下外边距合并

三.合并条件推论

根据外边距合并的发生条件,有8条推论:

简化总结 ,不过4条:

前3点针对“相邻”的前提条件(流内,同BFC,块级盒),第4点是对4种“相邻”场景的转述,展开就是8条推论

四.合并行为

两个相邻外边距发生合并后,形成的外边距叫折叠外边距

P.S.collapsed margin故意译作折叠表示结果,与合并的动作区分开

外边距合并有2个特点:

对于 递归 特性,“相邻”的定义扩展出一条递归公式:

折叠外边距也能与另一个外边距相邻,只要其外边距的任意一部分与那个外边距相邻就算

贪婪 与外边距合并结果计算方式有关,因为margin允许负值,情况稍微复杂一点:

例如:

ul {margin-bottom: -15px;}
  /* 缩进表示对应文档结的构嵌套关系 */
  li {margin-bottom: 20px;}
h1 {margin-top: -18px;}

那么 h1 与最后一个 li 的垂直距离为 20 + -max(|-15|, |-18|) = 2px

无论对正值还是负值,求最大值的原则都是让合并结果尽量宽(绝对值更大的负值能让元素内容偏移出去更远的距离),即 贪婪性

五.在线Demo

Demo地址: http://ayqy.net/temp/margin-collapse.html

P.S.答案都在Demo里,解释都在源码里

参考资料

作者:黯羽轻扬
原文地址:外边距合并规则, 感谢原作者分享。

发表评论