阅读 646

再读规范中浮动与定位细节

前言

如题,浮动与定位是CSS布局中重要且基础知识点,相关规范和书籍都有篇幅解读。但具体细节,笔者初读囫囵吞枣,几经再读受益匪浅。感叹规范,简明扼要,字字珠玑 。借此行文,着笔细节,竭尽所能,力求清晰。不过删繁就简,仍然又臭又长,自嘲厕所文。

在正文开始前,说几句题外话。以CSS 定位为例,浏览器搜关键字,点击阅读相关博文,你会发现大多通篇下来只是简单描述 position 属性值,及某种定位规则。其中对包含块(containing block)细节描述几近为零。如若对定位理解仅止步于此心满意足,一味追逐新技术好高骛远,岂不捡了芝麻丢了西瓜,得不偿失。

本文参考 CSS 2.1 规范,主要阐述浮动(float)与定位(position)相关细节,例如包含块定义、浮动细节、三列布局、清除浮动等。如有兴趣,烦请平心,慢慢阅读。

包含块(containing block)

笔者简单抛出几个问题,包含块定义是什么?建立包含块的元素一定是块级元素吗?包含块区域是由祖先元素的内容边界(content edge)还是内边距边界(padding edge)构成?

首先来看 CSS 2.1 规范对包含块(containing block)描述:

In CSS 2.1, many box positions and sizes are calculated with respect to the edges of a rectangular box called a containing block. In general, generated boxes act as containing blocks for descendant boxes; we say that a box "establishes" the containing block for its descendants.

简单翻译,“CSS 2.1中,很多盒的位置和大小是根据被称为包含块的矩形框的边计算的。一般把生成的盒作为后代盒的包含块,我们说一个盒为其后代“建立”了包含块。

通俗来讲,包含块(containing block)用来确定元素生成盒位置和大小。比如当元素指定 position 为除 static 外的值时,其偏移属性(right、top...)百分比是相对于包含块的宽度或高度计算。参照规范 10.1 Definition of containing block 章节,为节省篇幅,具体规则如图所示。

containing-block-details

稍作梳理,要点如下:

  1. 根元素包含块称为初始包含块,对于连续媒体为视口(viewport)尺寸,分页媒体是页区(page area)尺寸。

  2. 非根元素且 position 值是 staticrelative,包含块由最近的块容器block container )祖先盒的 内容边界(content edge)形成。

  3. 如果元素 position: fixed,包含块是由连续媒体的视口或分页媒体的页区建立。

  4. 如果元素 position: absolute,包含块由最近的 positionrelativeabsolute 或者 fixed祖先 (注:未限定是块容器)建立。区分以下情况:

    1. 如果祖先是行内元素,包含块是由该元素生成的第一个和最后一个行内盒的 内边距盒(padding box)形成。

    2. 否则,包含块由该祖先的 内边距边界(padding edge)形成。

根元素和元素为 position: fixed 情况较为简单,前者为后者生成初始包含块。细节在于 static 或 relativeabsolute 两者差异。static 或 relative 元素包含块为最近的 块容器 祖先盒的 content edgeabsolute 元素包含块为最近的 非 static 祖先盒padding edgepadding box ,再有 absolute 并未限定祖先为行内盒或块容器盒

对于浮动(float)元素,其包含块是其最近的块级祖先元素。(参照《CSS 权威指南》浮动与定位章节)

包含块(containing block)相关在工作中经常用到,细节熟稔于心才会从容自如。另外前面提及块容器(block container box)的概念以及与块级盒(block-level box)、块盒(block box)的区别可查阅 9.2.1 Block-level elements and block boxes 章节。

浮动理论

浮动(float)最有意思的特性是其他内容会沿着它的一侧排列。浮动元素会从文档的正常流中删除,不过还是会影响布局。例如图片浮动时,其他内容会“环绕”该元素。

浮动盒之前或之后创建的未定位的(non-positioned)块盒会正常排列,但接着浮动盒创建的当前及后续行盒会进行必要缩短,给浮动盒的 margin box 让出空间。位于浮动盒之前的当前行盒里的任何内容都会在浮动盒的另一侧的相同行重新排列reflowed)。

再有浮动元素会为其内容建立一个新的块级格式化上下文block formatting contexts)。表格,块级可替换元素或者常规流中建立新的块级格式化上下文的元素(例如一个overflow不为visible的元素)不能与同块级格式化上下文中的任何浮动盒的 margin box 重叠。

提一个问题,当浮动与正常流中的内容发生重叠会怎么样?比如浮动元素存在负外边距则有可能产生重叠。对此 CSS 规范指出明确规则:

  • 行内盒与一个浮动元素重叠时,其边框、背景和内容都在该浮动元素“之上”显示

  • 块盒与一个浮动元素重叠时,其边框和背景在该浮动元素“之下”显示,而内容在浮动元素“之上”显示

参考示例: codepen.io/sunpeijun/p…

此外,规范制定详细规则,用来控制浮动行为。如图所示,文字较多,如有兴趣,自行查阅。

浮动规则

上述截取自 CSS 2.1 规范 9.5.1 Positioning the float: the float property 章节。笔墨简短,却层次分明。简述三两条,左浮动盒左外边界或右外边界不能超出包含块的左边界或上边界,多个浮动元素间不会相互重叠等。建议阅读《CSS 权威指南》“第10章 浮动和定位”,有对每条规则详细解读。

再谈三栏布局

其实空读理论,犹如纸上谈兵。借着三栏布局场景,阐述 float 实现细节。首先限定布局结构:左中右三栏布局,左右两栏宽度固定,中间栏宽度自适应。以笔者所熟知的三栏布局,采用 float 实现可有两种方式:左右两栏浮动、三栏全浮动。

左右两栏浮动

<!-- 结构 -->
<div class="left"></div>
<div class="right"></div>
<div class="main"></div>
复制代码
/* 样式 */
 html, body { margin: 0; height: 100%; }
.main { height: 100%; margin: 0 210px; background: #ffe6b8; }
.left, .right { width: 200px; height: 100%; background: #a0b3d6; }
.left { float: left; }
.right { float: right; }
复制代码

参考示例: codepen.io/sunpeijun/p…

此种方式关键在于把主体标签放置最后,左右两栏 div 顺序任意。笔者曾尝试调整 main 元素顺序,均实验未果。那为什么一定要将主体标签放置最后?

规范 9.5 Floats 章节,存在一行文字:

If there is not enough horizontal room for the float, it is shifted downward until either it fits or there are no more floats present.

大意,“如果没有足够的水平空间来浮动,它会向下移动,直到空间合适或者不会再出现其它浮动”。结合块级元素会占满整行('margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = 包含块的宽度),后置位浮动元素在水平方向没有足够空间,所以不会漂浮上去。假如浮动元素前置脱离正常流,块级元素以浮动元素不存在正常排列。佐以规范辅证:

Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float did not exist.

注意 main 块盒也可用 overflow: hidden 替代 margin: 0 210px。如上节所述,overflow 不为 visible 的块盒会建立新的块级格式化上下文(BFC),同时不能与自身处于同一块格式化上下文中的任何浮动盒的 margin box 重叠。

注:上文引用自规范 9.5 Floats

三栏全浮动

三栏全浮动,有圣杯布局或双飞翼布局两种经典方式。当然实际为实现三栏布局采用上述,难免会有些脱离时代发展。使用绝对定位 或 flex布局方式,更优雅且易于理解。这里以双飞翼布局为例,解释其 float 实现细节。

<!-- 结构 -->
<div class="main">
  <div class="container"></div>
</div>
<div class="left"></div>
<div class="right"></div>
复制代码
/* 样式 */
 html, body { margin: 0; height: 100%; }
.main { float: left; width: 100%; height: 100%; }
.main .container { margin: 0 210px; height: 100%; background: #ffe6b8; }
.left, .right { float: left; width: 200px; height: 100%; background: #a0b3d6; }
.left { margin-left: -100%; }
.right { margin-left: -200px; }
复制代码

参考示例: codepen.io/sunpeijun/p…

需要注意,三栏全浮动方式主体元素顺序不再是重点,主体元素可前可中可后(样式需要微调)。笔者这里想表达,为什么左侧元素需要 margin-left: -100%,右侧元素要 margin-left: -200px

首先,两侧如果不采用负外边距会放置主体元素下方,因为没有足够的水平空间与主体并排。但两栏浮动元素设置足够负外边距,则可以使其移动至上方。横向对比,行内非替换元素也可以使用 marign-left 负外边距移动至上方行内盒区域(示例 codepen.io/sunpeijun/p…)。相反块级元素、行内替换元素不可使用 margin-left 负外边距使其产生垂直移动。

参考行内非替换元素特性,行内替换元素可以实现自动换行,例如文字过长会折行显示,同时浮动元素也可在水平空间不足情形,会自动向下移动寻找合适空间。如此类比,大概可描述多个浮动元素构成一个浮动流,内部可使用 margin 调整其位置。

声明: 以上解释笔者未找到权威资料佐证,如有错误,敬请指正。

至于具体负外边距值计算,可想象全部浮动元素水平排列。左栏元素浮动到目标区域需要往左移动整个包含块的宽度,也就是 margin-left: -100% 。右栏元素为什么要 margin-left: -200px?同理,左侧栏往上移动目标区域后,右栏正常会在主体元素下方,靠近包含块左边。按上述思路,只需设置自身宽度的负外边距即可放置右侧目标区域。

此外将主体元素放置在左右两栏中间,需要为 .main 添加 margin-left: -200px;,同时 .left 不在需要 margin-left: -100%;。示例见:codepen.io/sunpeijun/p…。主体元素放置最后情形,示例见: codepen.io/sunpeijun/p…

清除浮动

有时浮动元素会导致父元素高度坍塌,例如父元素中子元素浮动,并且没有设置高度。那如何防止高度塌陷?先列出相关理论,读后应该会有思路。

浮动,绝对定位元素,非块盒的块容器(例如,inline-blockstable-cellstable-captions)和 overflow 不为 visible 的块盒会为它们的内容建立一个新的块级格式化上下文。

一个表格,块级可替换元素或者常规流中建立了新的块格式化上下文的元素(例如一个 overflow 不为 visible 的元素)不能和与元素自身处于同一块格式化上下文中的任何浮动盒的 margin box 重叠。

笔者偷个懒,简单列出几种方式,请参考上述理论自行理解。

  • 添加额外标签设置 clear: both

  • 伪元素设置 clear: both

  • 包含块设置 overflow 不为 visible

  • 包含块浮动

  • 包含块绝对定位

display、position、float之间的关系

displaypositionfloat 属性相互影响盒的生成及布局,顺道说下三者关系。参照规范 9.7 Relationships between display, position, and float 章。

  1. 如果 display 值为 none,那么 positionfloat 不会生效。此时元素不生成盒。

  2. 否则如果 position 值为 absolute || fixed,盒为绝对定位且 float 的计算值为 nonedisplay 根据下表来设置。

  3. 否则如果 float 值不为 none,那么盒浮动且 display 根据下表来设置。

  4. 否则如果该元素是根元素,display 根据下表来设置。

  5. 否则其它 display 属性值(计算值)就用指定值

指定值 计算值
inline-table table
table-row, table-row-group, table-column, table-column-group block
table-header-group, table-footer-group, table-cell, table-caption block
inline, inline-block block

结语

或许本文过于抓细节,钻牛角尖。但笔者认为,就是这些细小的知识点汇聚为技术道路上的绊脚石。脚踏实地沉淀每一个知识点,不积细流,无以成江河。

笔者不才,详读文档多日,将自认为重要且容易忽视细节整理成文。事实上浮动与定位依然还有很多知识点,只不过本文未能有所体现。仓促行文,脉络凌乱。对于文中未阐述清楚或表达有误之处,欢迎斧正,拱手谢过。如有困扰之处,可留言给笔者。

参考文档

CSS 2.1: www.w3.org/TR/2011/REC…

[译]CSS 2.1: www.ayqy.net/doc/css2-1/…

打个广告,欢迎关注笔者公众号

关注下面的标签,发现更多相似文章
评论