重新入门前端(3):浮动、BFC与流式定位

感觉是 CSS 玄学大全

Posted by Donggu Ho on 2017-05-16

调试 CSS 最大的敌人可以说就是浮动和定位相关的一切了:高度坍塌、外边距折叠、定位与流……初学的时候全靠 Chrome 调试反复试出来。作为一个高级人士,是不能接受自己这样的:)于是认真学习了一下相关基础。

流式定位中的 position

大家都知道 HTML 是使用流式定位的:从上到下,从左到右。使用write-mode可以更改流的方向,此先不表。每个块状元素(block)都会占据一个新行,而行内元素(inline)则不会开启一个新行。

position用来更改元素在流中的展示位置。

position: static | relative | absolute | fixed

static

默认值为static:元素在流中正常展示。

static

HTML结构用emmet表示:.item.item${$}*3

1
2
3
4
5
6
7
8
9
10
.item
width 100px
height 100px
opacity 0.5
.item1
background #fb8ea2
.item2
background #00c897
.item3
background #b6c83b

relative

relative意为相对定位;元素的占位仍然是默认位置,但其展示则相对于原位置进行偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.item
width 100px
height 100px
opacity 0.5
.item1
background #fb8ea2
.item2
background #00c897
/* 给 item2 添加了relative和偏移属性 */
position relative
top 50px
left 50px
.item3
background #b6c83b

relative

如图, item2使用了relative定位,使用top: 50pxleft: 50px进行了偏移;但它仍然在流中存在,且占位与static时一样;其邻近元素也都对其偏移并不“知情”。

absolute 与 fixed

这两个定位比较类似,但fixed在页面滚动过程中不会变化。设定了这两个属性的元素会使其跳出当前所在的流,流的其他元素会当它不存在;但它的默认位置仍然是原本的位置。

给item2应用position: absolutepositon: fixed后效果如下图:item2位置没有改变,但 item3 直接接在 item1 后面,与 item2 发生了重叠。

fixed & absolute

发生重叠时可以使用z-index属性规定显示层级,此处不表。


现在问题来了:假如给现在的 item2 添加 topleft 属性,其定位是基于哪里的呢?

当没有设置 top/bottom/left/right 时,元素是按其在流中的正常位置显示的;但如果设置了,就不一样了。fixed / absolute 的定位基准为:祖先元素中(从父元素往上数)第一个position不为static的元素,直到<body>

比如刚才的例子,给 item2 添加了top: 50px; left: 50px之后,由于这三个 div 直接属于 body,所以 item2 基于body 进行偏移:

absolute-float

那么我们给它加几个父节点:.box.a{a}>.box.b{b}>.box.c{c}>.item.item${$}*3

设置以下样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.box
padding 20px
.a
background #eee
.b
background #ddd
position relative // 设置 b 为 relative
.c
background #ccc
.item
width 100px
height 100px
opacity 0.5
.item1
background #fb8ea2
.item2
background #00c897
position absolute // 设置 item2 为 absolute,并设置偏移
left 100px
top 100px
.item3
background #b6c83b

效果如下:

明显能看到,item2 是以 b 容器作为基准进行定位的。这就是为什么通常使用 fix/absolute 定位的元素,父元素都会使用 relative 进行定位;否则就管不到自己儿子了。

其实不止是定位,fix/absolute 还有很多属性都会受到影响。比如当item2使用一个较大的偏移(top: 400px),由于不在流中所以不会被容器包裹:

此时让其父元素 c 应用属性overflow: hidden并无作用,item2仍然会显示;但给 c 加上positon: relative之后就能够成功隐藏了。(图我就不截了XD

浮动 float

元素浮动,万恶之源(不是)。

Float 和 Top/bottom/left/right 的区别:

  • float:仅对position: static的元素有效
  • top/bottom/left/right:仅对static的元素有效

相比 tblr 只是进行偏移,float 是用于给流内部的元素进行浮动,这个还是有差别的。用 Word 里面的人话来形容的话,tblr 的操作逻辑接近于“衬于文字上/下方”之后进行随意的位置设置;而 float 则是正经的文字绕排。一个举例:

1
2
3
4
<div class="box">
<div>假装是一张图</div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci aspernatur delectus deleniti earum harum inventore porro quae recusandae. Aperiam dolore est et illo laboriosam nisi odit porro qui vero voluptatum.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci aspernatur delectus deleniti earum harum inventore porro quae recusandae. Aperiam dolore est et illo laboriosam nisi odit porro qui vero voluptatum.</p>
</div>
1
2
3
4
5
6
7
8
9
.box
width 500px
padding 10px
background #ddd
div
float right
width 100px
height 100px
background #eee

效果如图:

使用 float 后,流内部的块状元素 div 浮动到了右侧,而下面的文字则对图片形成绕排。在正常情况下块状元素是会单独占据一行的,而 float 的设置则改变了这一设定;同时从绕排的位置可以看出,元素浮动后所在的位置就是其在流中占据的位置,不会出现跟 position: relative 类似的显示位置与占据位置不一致的现象。

但从高度而言,浮动的元素流的确在高度上“消失”了。在上面的例子中,.box的高度实际是由文字撑出来的。假如文字不够多的话,.box的高度就会小于图片高度,这就是非常常见的高度坍塌问题:

当然,假如包含的元素全部都是浮动的话就更不用说,高度直接坍塌为0了。

业界为了解决这个问题也是形成了非常多的老司机手法来进行清除浮动,加上要达成浏览器兼容问题形成了各种各样的 CSS Hack,可以说是业界老大难了。主要有以下流派:

  • 父元素也做成浮动,并给下一个元素添加clear: both保证子元素落在父元素内
  • 使用下面的css属性:

    1
    2
    3
    4
    .clearfix {
    overflow: hidden;
    zoom: 1;
    }

    其中zoom用于 IE6 兼容,overflow 则用于触发 BFC(下文会讲),算是比较常见的一种手法。

  • 分别使用zoom:after伪类进行设置
    代码懒得贴了,反正也是 CSS hack 的一种,对元素样式的影响小,兼容性不错,因此也挺受推崇。

问题是,这些方法都太不优雅了。进行一个清除浮动要额外加一堆属性也就罢了,问题是这些属性跟目的本身根本没有联系,完全不符合 CSS3 以来的语义化目标(这又是另一个话题惹)。奇技淫巧固然不错但是这种无用功显然可以避免嘛。于是 CSS3 就有了新属性值:flow-root(此处应有掌声)。不过在细说之前,还是要先讲 BFC。

BFC & flow-root

BFC,全称 Block Formatting Context,中文名块状上下文/块级格式化上下文。放心吧无论怎么写都不是人话

具体定义欢迎啃文档

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 是 HTML 渲染中的一个块状区域,有自己的渲染规则。简单来说,这个区域的存在能保证其内部的元素与外部相邻元素不产生交集与重叠。

特性:

  • 内部 Box 沿主轴方向依次摆放
  • 垂直方向距离由 margin 决定,相邻的 margin 会发生折叠
  • 子元素的 margin-start 与父元素的 border-start 相接
  • 不与 float box 叠加
  • 计算高度时,浮动元素参与计算

触发条件

  • float: 使用了非默认值
  • overflow: 使用了非默认值
  • display: table-cell | table-caption | inline-block | flex | inline-flex | flow-root
  • position: fixed | absolute

可以看出,前文中的 overflow: hidden 就是为了触发 BFC。但是存在希望 overflow 保持原始值的情况,而其它方法显然更加繁琐和缺乏语义。为此,CSS3 为 display 添加了新的属性flow-root(以及其它很多别的),其作用就是为元素触发一个 BFC。

新属性兼容情况比较惨淡,但是新版 Chrome 和 Firefox 都支持了,那么课程项目就可以用起来啦。

效果

还是上面高度坍塌的例子,只需要给.box添加display: flow-root即可。

需要注意的是,使用了 flow-root 属性建立了 BFC 后,由于 BFC 内的元素不会与外界发生交互,所以假如给文章段落触发 BFC,其图文绕排效果也消失了。

外边距折叠相关

垂直方向上的、普通流中相邻的两个块状元素,其 margin 会发生折叠(margin-collapse)。其实我觉得这很合理,只要正确设置margin就没有问题……但好像有人对此感到不满╮( ̄▽ ̄”)╭

具体解决方法……我会用 flex 【逃

并不相关的一个实用示例

某位搞艺术的同学提出了一个需求:需要一个圆圈在页面滚动过程中固定在页面的中心(水平和竖直)。

这显然就是用position: fixed了。但是应用了该属性之后,各种各样的居中方法仿佛都失效了:

  1. text-align: center

    由于元素是块状元素所以失效;如果把元素添加上display: inline-block倒也可以居中,但居中的是元素的左边距;而且由于该块状元素的父元素就是body,给body应用text-align: center会影响到其它元素。

  2. margin: auto 总之就是没反应

  3. flex 布局

    虽然说 Flex 大法好,实现居中的确非常轻松,但是在这个使用情景下,要么就要给 body 应用 flex 然后把剩下的元素搞乱,要么就要给块状元素包裹一层 100%*100% 的 div,然后下面层的点击事件全部挂掉,虽然可以用pointer-events: none吧但是其实也不太科学’_>’

【使用了relative也并无卵用

于是找到的比较优秀的水平居中方案:

1
2
3
4
5
6
.center-box
position fixed
left 0
right 0
margin auto
z-index 9

垂直+水平居中方案:

1
2
3
4
5
6
.center-box
position fixed
left 50%
right 50%
transform translate(-50%, -50%)
z-index 9

有问题还是要靠 stackoverflow 啊孩子们