理解css中的display

提炼css-display这个module中的一些要点。

css box tree

css把一个包含elementstext nodesdocument tree渲染到canvas上。
css生成了一个中间结构:box tree,表示document tree的格式化渲染结构。
box tree上的每个box与相应的element or pseudo element对应,box tree上的每个text runtext nodescontent对应。

对每个element而言,css会根据它的display属性值为它自身创建0到多个box。 一个element至少会有一个box,称为principal box,表示element自己以及它在box tree中包含的内容。 有一些display值会生成多个box。比如list-item这个值,会生成一个principal block box和一个marker box。有一些值比如none or contents不会生成任何boxbox可以用display的类型来指代,比如一个display:blockelement可以被称为一个block box或者block

除非另有说明,box会被设定与它对应的element相同的样式。inherited properties(可继承的属性)被设定到elementprincipal box上,然后通过box tree的层级关系继承给该element生成的后代boxnon-inherited properties(不可继承属性)默认情况下也是应用到principal box上,但是如果一个element自身要生成多个box,就可能把non-inherited properties应用到其它box,而不是principal box。比如table元素的border属性,是一个non-inherited property,它是应用到table grid box上的,不是table wrapper box。补充:tableprincial box又称为table wrapper box

document tree中连续的text nodescss会为它们生成一个text run的东西,来包含这些文本内容,每个text run会被设定成与它们对应的text nodes相同的样式。

在创建box tree的过程中,某一个element生成的boxes(应该指所有)都是这个元素所有祖先elementsprincipal box的后代。通常来说,一个元素的principal boxdirect parent box就是这个元素祖先元素中,离它最近并且有生成box的那个祖先元素的principal box;但是有例外,比如下面的anonymous box的情况。补充理解:direct parent box的含义,也可以理解为一个元素的principal boxdirect parent box,大部分情况应该就是它父级元素的principal box

anonymous box就是与任何element都没有关系的boxanonymous box都是在特定情况下才会生成用来修复box tree结构;这个特殊情况是指box tree需要一个特别的box嵌套结构,但是这个嵌套结构没有被element tree提供。 比如,一个table cell box要求它的parent box必须是一个tabel row box,所以如果一个display:table-cellelement父级,并不是一个display: table-rowelement时,那此时element tree就没有满足table row box -> table cell box这种嵌套结构,css会自动在table cell box的外层生成一个table row box来作为它的parent box

在布局过程中,boxtext run可能会被分割为多个fragments。比如,当一个inline box或者是text run因为换行,就会被分割为多个fragments;一个block box也会因为page(分页)和columns(分栏),被分割为多个fragments;这个分割过程叫做fragmentation。因此一个box可能会存在0到多个box fragments,一个text run可能存在0到多个text fragments

display property

display定义了elementdisplay type,包含两个方面:

  • inner display type 决定它自己生成一个什么类型的formatting context,控制由它生成的descendant boxes如何布局;
  • outer display type 决定element自己的principal box如何参与flow layout

注意此要点的描述:outer display type决定的是自己如何参与flow layout,而css中并不只有一种layout,除了flow layout,还有flex layouttable layout等,那么当一个元素位于一个flow layout当中,outer display type才有意义,否则没有意义。比如当元素位于flex layouttable layout当中,outer display type就没有意义。

value

displayvalue定义:

1
[ <display-outside> || <display-inside> ] | <display-listitem> | <display-internal> | <display-box> | <display-legacy>

各个value component的定义:

1
2
3
4
5
6
7
8
9
10
<display-outside>  = block | inline | run-in
<display-inside> = flow | flow-root | table | flex | grid | ruby
<display-listitem> = <display-outside>? && [ flow | flow-root ]? && list-item
<display-internal> = table-row-group | table-header-group |
table-footer-group | table-row | table-cell |
table-column-group | table-column | table-caption |
ruby-base | ruby-text | ruby-base-container |
ruby-text-container
<display-box> = contents | none
<display-legacy> = inline-block | inline-table | inline-flex | inline-grid

根据以上语法说明,以下的value形式都是允许的:

1
2
3
4
5
6
7
8
display: <display-outside>;
display: <display-inside>;
display: <display-outside> <display-inside>;
display: <display-inside> <display-outside>;
display: <display-listitem>;
display: <display-internal>;
display: <display-box>;
display: <display-legacy>;

特殊的display-listitem还会出现下面这种奇怪的display值写法:

1
2
3
display: block flow list-item;
display: list-item;
display: block list-item;

以上value type涉及到的<> || | []以及下面的&& ?等符号的语法,是css在规范文档中用来描述Value Definition的一种语法,可前往css-values这个文档学习。

综合以上内容可以看到,display的值是支持单值、双值和三值写法的:

1
2
3
display: block flow;
display: block flow list-item;
display: inline-block;

不过目前这个写法还没有得到支持,但是可以从这个角度去理解display,因为多值的写法能够清晰地看到inner display typeouter display type,大部分单值写法只是多值写法的简写形式。

display-outside

<display-outside>这个value type,决定了displayouter display type,它包含3个值:

1
<display-outside>  = block | inline | run-in

其中run-in并没有完全支持,所以暂不讨论。另外两个含义如下:

  • block 表示当元素处于flow-layout当中时,生成的boxblock-level
  • inline 表示当元素处于flow-layout当中时,生成的boxinline-level

要分析一个display值的outer display type只要找到display值里面跟<display-outside>对应的部分即可。当讨论一个boxblock-level还是inline-level,说的是boxouter display type

display-inside

<display-inside>这个value type,决定了displayinner display type,包含以下几个值

1
<display-inside>   = flow | flow-root | table | flex | grid | ruby

其中ruby并没有完全支持,所以暂不讨论。其它值的含义如下:

  • flow 表示该元素的包含的内容,会使用flow layout进行布局,flow layout就是block-and-inline layout,它包含两种formatting context: block formatting contextinline formatting context。在display-insde: flow的前提下,当outer display typeinline时,生成的box是一个inline box;当outer display typeblock,生成的box是一个block-levelbox,称为block containerblock-containerblock box不完全相同,block box是突出一个boxblock-level的,而block-container是突出一个box的能力,见下面对block container的解释。当一个boxinline box的时候,它始终带着自己和自己包含的内容参与到一个inline formatting context当中一起布局;当一个box是一个block container的时候,它要么会新建一个BFC来布局子内容,要么把子内容放到自己所在的那个block formatting context中一起布局。
  • flow-root 这个跟flow唯一的区别就是,它一定会让box新建一个BFC。那种新建了BFCblock container都可以当成inner display typeflow root来看待。
  • table 表示元素会生成一个principal box,也叫做table wrapper box,并且新建一个block formatting context,在此context内再生成一个table grid box,并且使用一个新的布局context:table formatting context来布局内容,这就是所谓的table layouttable wrapper box应该是一个block container,因为它会创建BFC
  • flex 表示元素会生成一个flex container box,并创建一个新的布局context:flex formatting context,使用flex layout来布局内容。这个flex containerblock container是完全不同的,flex container指的是这个box是1个flex formatting context的根。
  • grid 表示元素会生成一个grid container box,并创建一个新的布局context:grid formatting context,使用grid layout来布局内容。这个grid containerblock container是完全不同的,grid container指的是这个box是1个grid formatting context的根。

block container

block container box要么只包含inline box,要么只包含block box。怎么做到这一点呢?需要一个box只包含inline-box比较容易,但是要它只包含block-box如何做到?实际上还是借助anonymous box,比如下面这个结构:

1
2
3
4
<div>
asd
<p>abc</p>
</div>

div内混合了文本和p节点,css会在asd外面包裹一个anonymous block box来保证div这个block container仅包含block box

当一个block container只包含inline box的时候,它会创建一个inline formatting context,并且会在所有的inline box外面包裹一层anonymous box来作为这些inline contentroot inline box

block container在它的parent formatting context不是一个block formatting context的时候,会创建一个新的block formatting context来布局它的内容。这个点值得关注,下面这个例子能证明这个点是对的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
.parent {
width: 400px;
background-color: #ddd;
margin: 0 auto;
}

.child {
width: 100px;
height: 100px;
background-color: red;
float: left;
}

.main {
display: flex;
}
</style>
</head>
<body>
<div class="main">
<div class="parent">
<div class="child"></div>
</div>
</div>
</body>
</html>

例子中,div.main是一个flex formatting context,而div.parent是一个block container,所以div.parent会创建一个BFC,这样div.child浮动带来的高度塌陷问题就没有了。

如果block containerparent formatting context是一个BFC,那么block container就是按两种方式来布局它包含的内容:第一种,还是新建一个BFC,比如overflow float position这些属性可能会导致新的BFC被创建;第二种,就是将它的内容放到parent formatting context中去布局。第二种方式,有一个场景值得说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
.parent {
width: 400px;
background-color: #ddd;
margin: 0 auto;
}

.child {
width: 100px;
height: 100px;
background-color: red;
float: left;
}
</style>
</head>
<body>
<div class="parent">
<div>yes</div>
<div>yes</div>
<div>yes</div>
<div>yes</div>
<div>yes</div>
<div class="child"></div>
</div>
<p>surrounds</p>
</body>
</html>

上例中,为什么与div.parent同级的p元素会环绕在div.child这个浮动元素的右边呢?这是因为上面的例子中div.parent并没有创建新的BFC,所以它的内容都是在bodyformatting context中布局出来的,而div.child虽然设置了浮动,但是浮动只能让它从文档流脱离出来,它仍然属于bodyblock formatting context,而div.parent后面的p也是在bodycontext中布局的,这样p里面的文本所形成的inline formatting context与浮动元素的作用关系,导致了这个结果。浮动本质上不会脱离元素所在的block formatting context。如果想让pdiv.child不产生浮动环绕的作用,解决办法就是把div.parent变为BFC,这样div.childp不在同一个block formatting context,它们是不会产生浮动环绕作用的。

block container可以同时有两种formatting context,比如它仅包含inline box,这样它会创建inline formatting context,然后再通过别的方式触发新建BFC,这样就两种formatting context共存了。

block containervsblock box

  • block box是指block-level box,它参与flow layout时一定是block-level的,它不一定是block container,比如display: blockreplace elements所生成的box以及display: flex的元素生成的flex container都是block box,但不是block container
  • block container也不一定都是block box,比如display: inline-blockdisplay: table-cell的元素生成box会创建BFC,属于block container,但不是block-level box

缺省说明

如果display值,指定了<display-outside>但是没有指定<display-inside><display-inside>的默认值就是:flow
如果display值,指定了<display-inside>但是没有指定<display-outside><display-outside>的默认值就是:block

display-listitem

这个value type的定义是:

1
<display-listitem> = <display-outside>? && [ flow | flow-root ]? && list-item

其中<display-outside>[flow | flow-root]是可选的,list-item这个关键词是必须有的。<display-listitem>inner display type限制为flow | flow-root,暂时没有其它的inner display type。如果未指定outer display type,则outer display type默认值是:block,所以li元素默认都是block-level的;如果未指定inner display type,则inner display type默认值是:flow。 所以<display-listitem>可定义的display值的形式有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* a. */
display: list-item;

/*以下3个与a.均等价*/
display: block flow list-item;
display: flow list-item;
display: block list-item;

/*以下两个等价*/
display: inline list-item;
display: inline flow list-item;

/*以下两个等价*/
display: flow-root list-item;
display: block flow-root list-item;

从上面的举例也能看到,display: list-item这种单值写法是多值写法的简写形式。

display-listitem相比<display-outside> || <display-inside>,最大的区别其实是它会让element多生成一个marker box来表现项目列表的符号,这就是为啥li元素默认情况下,前面会有项目符号的原因。

display-legacy

这个value type定义为:

1
<display-legacy> = inline-block | inline-table | inline-flex | inline-grid

这几个关键词就是几个简写形式:

  • inline-block 等价于inline flow-root,所以display:inline-block的元素其实是个BFC
  • inline-table 等价于inline table
  • inline-flex 等价于inline flex
  • inlin-grid 等价于inline grid

display-internal

这个value type的定义:

1
2
3
4
5
<display-internal> = table-row-group | table-header-group |
table-footer-group | table-row | table-cell |
table-column-group | table-column | table-caption |
ruby-base | ruby-text | ruby-base-container |
ruby-text-container

一些布局(例如table ruby)具有复杂的内部结构,其子代和后代可以担当几种不同的角色,display-internal实际上就是在指定元素在table ruby这种特定布局里面充当的角色,所以display-internal跟其它的displayvalue type不一样,它只有在特定的布局里面才有意义。而且非常特殊的是,display-internal的值,除非另有说明,否则使用这些值的元素生成的box的inner display typeouter display type都将设置为给定关键字。比如display: table-row-group,这个元素的inner display typeouter display type就都是table-row-group。 所以前面好多的关于outer display typeinner display type的知识,在display-internal不一致的,这是css针对table ruby布局的内部处理。

不考虑rubydisplay-internal这个值的详细含义如下:

  • table-row-group, table-header-group, table-footer-group, table-row, table-cell, table-column-group, table-column 表示这个元素是一个table布局的子元素,它会相应的创建internal table box来参与table layout。特殊的是,table-cell指定了inner display type为:flow-root,所以display:table-cell的元素会创建BFC

  • table-caption 表示这个元素会生成一个table caption box,它是一个block box,并且和table and table wrapper boxes有特殊的行为,同时它还有指定inner display type为:flow-root,所以display:table-caption的元素也会创建BFC

归纳总结

综合以上内容,发现:学习display的值,主要抓这几个要素:

  1. 分析它生成的box,1个还是多个,每个box的名称是啥
  2. 分析它的inner display type,遵循什么layout,会不会新建formatting context
  3. 分析它的outer display type,看看是block-level还是inline-level

下面是一个对常见display值的归纳整理:

  • display:block

    完整写法:display: block flow
    outer display type: block
    inner display type: flow
    box level: block-level
    生成的box:一定是block box 不一定是block container,见前面对此二者的区分说明
    formatting context: 要么新建一个BFC布局子内容,要么把子内容布局到自己所在的BFC

  • display:flow-root

    完整写法:display: block flow-root
    outer display type: block
    inner display type: flow-root
    box level: block-level
    生成的box:block container
    formatting context: 新建一个BFC布局子内容

  • display:inline

    完整写法:display: inline flow
    outer display type: inline
    inner display type: flow
    box level: inline-level
    生成的box:inline box
    formatting context: 将子内容布局到自己所在的inline formatting context

  • display:inline-block

    完整写法:display: inline flow-root
    outer display type: inline
    inner display type: flow-root
    box level: inline-level
    生成的box:block container
    formatting context: 新建一个BFC布局子内容

  • display:list-item

    各个要素与前面四个几乎一致,就是会多生成一个marker box

  • display:flex

    完整写法:display: block flex
    outer display type: block
    inner display type: flex
    box level: block-level
    生成的box:flex container
    formatting context: 新建一个flex formatting context布局子内容

  • display:inline-flex

    完整写法:display: inline flex
    outer display type: inline
    inner display type: flex
    box level: inline-level
    生成的box:flext container
    formatting context: 新建一个flex formatting context布局子内容

  • display:grid

    完整写法:display: block grid
    outer display type: block
    inner display type: grid
    box level: block-level
    生成的box:grid container
    formatting context: 新建一个grid formatting context布局子内容

  • display:inline-grid

    完整写法:display: inline grid
    outer display type: inline
    inner display type: grid
    box level: inline-level
    生成的box:flext container
    formatting context: 新建一个grid formatting context布局子内容

  • display:table

    完整写法:display: block table
    outer display type: block
    inner display type: table
    box level: block-level
    生成的box:table wrapper box包含table grid box,其中table wrapper box是一个会新建BFCblock-container,且是block-level
    formatting context: table wrapper box新建BFC,而table grid box新建table formatting context

  • display:inline-table

    完整写法:display: inline table
    outer display type: inlne
    inner display type: table
    box level: inline-level
    生成的box:table wrapper box包含table grid box,其中table wrapper box是一个会新建BFCblock-container,且是inline-level
    formatting context: table wrapper box新建BFC,而table grid box新建table formatting context

display type的自动转换

blockification: 块级化,将boxouter display type强制设定为block
inlinification: 内联化,将boxouter display type强制设定为inline

一些布局可能会对元素的box进行blockification或者是inlinification的处理,比如浮动 或绝对定位 或flex布局都会对元素进行blockification

引用

https://www.w3.org/TR/2019/CR-css-display-3-20190711/