一、Box
Box 是 CSS 布局的对象和基本单位, 一个页面是由很多个 Box 组成的。
( 显示页面所有盒子,请在控制台中输入:
[].forEach.call(document.querySelectorAll('*'), function(a){a.style.outline = "1px solid red";})
)
Box 的类型:由元素的类型和 display 属性决定。
不同类型的 Box, 会参与不同的 Formatting Context(一个决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染。
盒子类型 | 属性及特性 | 参与FC |
---|---|---|
block-level box | display :block / list-item / table 的元素 | block fomatting context |
inline-level box: | display:inline / inline-block / inline-table 的元素 | inline formatting context |
每一个盒子都被划分为四个区域:
- Margin外边距区
- Border边框区
- Padding内边距区
- Content内容区
二、包含块
元素最近的祖先块元素(inline-block, block 或 list-item 元素)的内容区
(与盒模型类似,也可以理解为一个矩形,但 包含块不是一个完整的box)
作用: 为它里面包含的元素提供一个参考,元素的尺寸和位置的计算往往是由该元素所在的包含块决定的。简单的说谁是谁的包含块,谁是谁的参照系
2.1 确定一个元素的包含块
包含块可能是box的content包含块,也可能是box的padding包含块。
这取决于被包含块所包含的元素的 position 属性:
- * 为 static 、 relative 或 sticky,包含块可能由它的 最近的祖先块元素(比如说inline-block, block 或 list-item元素)的 内容区的边缘组成,也可能会建立格式化上下文。
- 如果 position 属性是 fixed,包含块是 视口viewport。
- * 如果 position 属性为 absolute ,包含块就是由它的 最近的 position 的值不是 static 的祖先元素的内边距区的边缘组成。
- 如果 position 属性是 absolute 或 fixed,包含块也可能是由满足以下条件的最近父级元素的内边距区的边缘组成的:
transform 或 perspective 的值不是 none
will-change 的值是 transform 或 perspective
filter 的值不是 none 或 will-change 的值是 filter(只在 Firefox 下生效).
contain 的值是 paint (例如: contain: paint;)
2.2 EXAMPLE
<style>
*{
margin: 0;
}
section {
position: absolute;
width: 400px;
height: 160px;
background: lightgray;
}
p {
position: absolute;
width: 50%; /* == (400px) * .5 = 200px */
height: 25%; /* == (160px) * .25 = 40px */
background: cyan;
}
</style>
<body>
<section>
<p>This is a paragraph!</p>
</section>
</body>
这个例子中p
的position为absolute,所以应从内向外找到最近的postion不为static的元素,这里找到了父级盒子section,其postion值为absolute,故p
的包含块为section
三、FC(Formatting Context)
格式化上下文,是页面中的一块渲染区域,并且有一套渲染规则,它决定了子元素如何定位,以及与其他元素的关系及相互作用。
存在四种类型的FC:
-
BFC( Block Formatting Context | 块级格式化上下文)
-
IFC( Inline Formatting Context | 行内格式化上下文)
-
GFC( Grids Formatting Context | 网格格式化上下文)
-
FFC( Flexible Formatting Context | 弹性盒格式化上下文)
其中,GFC和FFC就是CSS3引入的新布局模型——grid布局和flex布局~
四、BFC
块级格式化上下文,是用于 布局块级盒 的一块 渲染区域。
只有Block-level box参与, 它规定了内部的Block-level Box如何布局。
1. BFC的形成条件
- 根元素
- float:不为none
- position:不为static或relative
- display:inline-block / flex / inline-flex / flow-root / table-caption / table-cell。
- overflow:不为visible (为hidden、scroll和auto)
2. BFC的布局规则
- 内部的box会在垂直方向上,一个接一个的放置。
- box垂直方向的距离由margin决定,属于 同一个BFC的两个相邻box的margin会发生重叠 (margin塌陷)
- 每个盒的左外边界挨着包含块的左外边界,即使存在浮动
- bfc的区域不会与float元素重叠。所以可以制造bfc区域,使其与浮动元素贴边,而此时也会撑起父元素的高度。
- bfc就是页面上一个隔离的独立的容器,容器内的子元素不会影响到外面的元素,反之亦然。
- 计算bfc的高度时,浮动元素也参与计算。可以理解为,让父元素形成一个bfc区域时,里面的浮动元素会撑起父元素的高度。
3. bfc的作用与原理
①自适应两栏布局的实现
before:
布局规则第3条:每个盒的左外边界挨着包含块的左外边界,即使存在浮动
<style>
body {
position:relative;
}
.aside {
width: 100px;
height: 140px;
float: left;
background-color: gold;
}
.main {
height: 200px;
background-color: green;
}
</style>
<body>
<div class = "aside"></div>
<div class = "main"></div>
</body>
因此,即使存在浮动的aside,main的左边依然会与其包含块(此处指body)的左边相接触。
正常情况下,元素main会与元素aside重叠(因为float导致aside元素脱离文档流、不再占据原来的位置,后面元素会占据前面的位置,如图:
但如果我们并不想要main
环绕aside
,如何实现两栏布局呢?
after:
布局规则第4条:BFC的区域不会与float box重叠。
我们可以通过通过触发main生成BFC, 来实现自适应两栏布局,实现如下:
.main{
overflow: hidden;
}
自适应两栏最终效果图:
② 防止 margin 重叠
before:
布局规则第2条:Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
<style>
.box {
margin-left: 50%;
overflow: hidden;
width: 100px;
border: 1px solid red;
.box1,.box2 {
width: 100px;
height: 100px;
}
.box1 {
margin-bottom: 30px;
}
.box2 {
margin-top:20px;
}
}
</style>
<body>
<div class = "box">
<div class = "box1">box1</div>
<div class = "box2">box2</div>
</div>
</body>
两个盒子的间距取了margin-bottom和margin-top之间的最大值30px,发生重叠。
after:
上述情况也称边距塌陷,我们应该如何解决塌陷的问题?
这里可以给 box1或者box2外层套一个 BFC 的盒子就可以解决:
.wrap {
overflow: hidden;
}
<div class = "wrap">
<div class = "box1">box1</div>
</div>
布局规则第5条:bfc就是页面上一个隔离的独立的容器,容器内的子元素不会影响到外面的元素,反之亦然。
可以看到,这时两个盒子的外边距没有发生重叠,间距变为50px!
③ 清除内部浮动
清除浮动主要是为了解决:父元素因为子级元素浮动引起的内部高度为0的问题
before:
首先,页面上有一个父盒子内部放有left、right两个子盒子,不设置浮动,则他们会默认撑开父盒子,父盒子下有一个footer盒子。
<style>
.father {
width: 600px;
border: 5px solid black;
.left {
width: 300px;
height: 200px;
background-color: green;
}
.right {
width: 200px;
height: 100px;
background-color: gold;
}
}
.footer {
width: 600px;
height: 50px;
background-color: hotpink;
}
</style>
</head>
<body>
<div class = "father">
<div class = "left">left</div>
<div class = "right">right</div>
</div>
<div class = "footer">footer</div>
</body>
此时,给left、right添加浮动:
.left {
float:left;
}
.right{
float:left;
}
此时父盒子因为没有任何子盒撑开高度为0,只剩下黑色边框,left、right两个子盒子覆盖在了footer上:
after:
父盒子没了高度这就是高度坍塌,要解决这个问题,我们可以使用bfc的方法清除浮动
布局规则第6条:根据BFC的规则计算BFC的高度时,浮动元素也参与计算
.fahter{
overflow: hidden;
}
我们可以让父盒子触发bfc,最终实现清除浮动效果:
缺点:内容增多的时候容易造成不会自动换行导致内容被隐藏掉,无法显示要溢出的元素
当然,清除浮动在不同的情况下使用的方法也不一样,应视具体情况而行。
清除浮动方法:
- 额外标签法,添加
clear:both
- 使用after伪元素 (推荐)
- 父级添加
overflow:hidden
- 浮动外部元素
五、IFC
内联格式化上下文,是用于 布局内联元素 盒子的一块 渲染区域。
1. IFC的形成条件
块级元素中仅包含内联级别元素
2. IFC的布局规则
-
盒是从包含块的顶部开始一个挨一个水平放置的
-
水平
padding、border、margin
都有效,垂直方向上不被计算。 -
在垂直方向上,子元素会以不同形式来对齐
vertical-align
-
能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的行框(line box)。行框的宽度是由包含块(containing box)和与其中的浮动来决定。
-
IFC中的“line box”一般左右边贴紧其包含块,但float元素会优先排列。
-
IFC中的“line box”高度由 CSS 行高计算规则来确定,同个IFC下的多个line box高度可能会不同。
-
当 inline-level boxes的总宽度少于包含它们的line box时,其水平渲染规则由
text-align
属性值来决定。 -
当一个“inline box”超过父元素的宽度时,它会被分割成多个boxes,这些 boxes 分布在多个“line box”中。如果子元素未设置强制换行的情况下,“inline box”将不可被分割,将会溢出父元素。
相比较于BFC,IFC的规则太杂太多,举几个例子,花几分钟就可以大概明白其特性。
3.example
①垂直间距不生效
布局规则第1条:盒是从包含块的顶部开始一个挨一个水平放置的
<style>
.wrap {
border: 1px solid black;
display: inline-block;
}
.text {
background: red;
}
</style>
<div class = "wrap">
<span class = "text">文本一</span>
<span class = "text">文本二</span>
</div>
布局规则第2条:padding、border、margin水平有效,垂直方向上不被计算
给子元素加上一个四个方向的margin属性 .text { margin: 30px; }
发现水平方向上两个子元素水平两侧的间距变为了30px,但是垂直方向上并没有任何改变。
② 元素垂直居中
布局规则第3条:在垂直方向上,子元素会以不同形式来对齐
vertical-align
想要实现文字与图片垂直居中,只要给图片加上一个vertical-align: middle
属性就可以实现:
在最大高度的元素上使用负值(middle = - 50% * 元素高度),可以提升基线位置
③多个元素水平居中
布局规则第7条:当 inline-level boxes的总宽度少于包含它们的line box时,其水平渲染规则由
text-align
属性值来决定。
<style>
.wrap {
border: 1px solid black;
width: 200px;
text-align: center;
}
.text {
background: red;
}
</style>
<div class = "wrap">
<span class = "text">文本一</span>
<span class = "text">文本二</span>
</div>
<div>
元素内部有两个 <span>
元素,由于 <div>
宽度200px,并且内部的 <span>
元素总宽度小于 200 像素,行盒子的宽度将大于内部元素的宽度。
因此遵循 text-align: center;
,内部元素将在行盒子中水平居中对齐,如下所示:
④浮动元素优先排列
布局规则第4条:能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的行框(line box)。行框的宽度是由包含块和与其中的浮动来决定。
- 行框
布局规则第6条:IFC中的 “line box” 高度由 CSS 行高计算规则来确定, 同个IFC下的多个line box高度可能会不同。
给图片增加一个浮动属性,img { float: left; }
,after:
布局规则第4条:行框的宽度是由包含块和与其中的浮动来决定:
- 没有float元素干扰的情况下,宽度等于包含块的宽度
- 有float元素的时候,减去float元素的宽度.
六、FFC
弹性格式化上下文
CSS3引入了一种新的布局模型——flex布局。
flex是flexible box的缩写,一般称之为弹性盒模型。
flex布局提供一种更加有效的方式来进行容器内的项目布局,以适应各种类型的显示设备和各种尺寸的屏幕,使用Flex box布局实际上就是声明创建了FFC(自适应格式上下文)。
注意一点:生成FFC后,其子元素的float、clear和vertical-align属性将失效。
1.FFC产生条件
display: flex / inline-flex
的容器
2.应用场景
1、自动撑开页面高度,底栏总是出现在页面的底部
before:
<style>
.wrap {
text-align: center;
min-height:100vh;
}
.header {
height: 50px;
background-color: rgb(0, 131, 0);
}
.footer {
height: 30px;
background-color: rgb(124, 124, 124);
}
</style>
<div class = "wrap">
<div class = "header">顶部导航栏</div>
<div class = "main">内容</div>
<div class = "footer">底部</div>
</div>
after:
给最外层盒子设display:flex
,使用flex布局,将内容盒子设为flex:1
,使得内容盒子撑开剩余的高度:
.wrap {
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
可以看到,内容盒子撑开了剩余的高度:
3.拓展:flex简写属性是如何计算的?
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。
flex-grow
flex-grow
属性决定了父元素在空间分配方向上还有剩余空间时,如何分配剩余空间.
其值为一个权重(也称扩张因子),默认为 0(纯数值,无单位),剩余空间将会按照这个权重来分配
计算方式:剩余空间为 x,三个元素的 flex-grow 分别为 a,b,c,设 sum 为 a + b + c。
那么三个元素将得到剩余空间分别是 x * a / sum
,x * b / sum
,x * c / sum
。
举例:
父元素宽度 500px,三个子元素的 width 分别为 100px,150px,100px, flex-grow 分别是 1,2,3。
解:
可知剩余空间为150px
,sum = 1+2+3= 6
,则三个元素所分配到的多余空间分别是:
150 * 1 / 6 = 25px
150 * 2 / 6 = 50px
150 * 3 / 6 = 75px
三个元素最终的宽度分别为:
100px + 25px = 125px
150px + 50px = 200px
100px + 75px = 175px
根据上述原理计算,就能知道为什么使用flex-grow:1
可以让main盒子占据剩下的位置了~
以上仅对于a+b+c ≥ 1的情况有效
flex-shrink
flex-shrink
可以在空间不够时让各个子元素收缩以适应有限的空间。
其值默认为1,但每个元素具体收缩多少,还有另一个重要因素,即它本身的宽度。
计算方式:
溢出空间为 |x|,三个元素的 宽度为w1,w2,w3,flex-shrink分别为 a,b,c。
总权重sum= w1*a + w2*b + w3*c
,那么三个元素收缩空间分别是 |x| * (a*w1) / sum
,|x| * (b*w2) / sum
, |x| * (c*w3) / sum
。
举例:
父元素 500px,三个子元素分别设置为 150px,200px,300px, flex-shrink 的值分别为 1,2,3。
解:
子元素溢出空间:150 + 200 + 300 - 500 = |-150px|
,溢出量将由各元素的相应flex-shrink
值来弥补。
总权重为 1 * 150 + 2 * 200 + 3 * 300 = 1450
三个元素分别收缩:
150 * 1(flex-shrink) * 150(width) / 1450 = -15.5
150 * 2(flex-shrink) * 200(width) / 1450 = -41.4
150 * 3(flex-shrink) * 300(width) / 1450 = -93.1
三个元素的最终宽度分别为:
150 - 15.5 = 134.5
200 - 41.4 = 158.6
300 - 93.1 = 206.9
以上仅对于a+b+c ≥ 1的情况有效
七、GFC
网格布局格式化上下文
CSS3引入的一种新的布局模型——Grids网格布局。
Flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局。
Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局。
1.GFC产生条件
display: gird / inline-grid
的容器
2.用法
grid-template-areas
属性在网格布局中规定区域。
您可以使用 grid-area
属性命名网格项目,然后在 grid-template-areas
属性中引用该名称。
举例:
<style>
.layout {
display: grid;
width: 500px;
height: 400px;
grid-template-areas:
"header header header"
"sidebar content content"
"footer footer footer";
}
.header {
grid-area: header;
background: chocolate;
}
.aside {
grid-area: sidebar;
background: crimson;
}
.main {
grid-area: content;
background: darkgreen;
}
.footer {
grid-area: footer;
background: blue;
}
</style>
<div class = "layout">
<div class = "header">头部</div>
<div class = "aside">侧边栏</div>
<div class = "main">内容</div>
<div class = "footer">底部</div>
</div>
实现效果如下: