1. 前言
上周电话面试某游戏公司,开局第一个问题就是 圣杯布局与双飞翼,我以前看过,不过没放在心上,当面试官问我的时候,我只能想起 是左右定宽,中间自适应, 我知道有5种实现该布局的方式,但是不知道圣杯与双飞翼是哪一种,哎,回去看了这两个布局的资料,诶,发现,圣杯与双飞翼 是另外两种。本文首发于 www.catac.cn , 联系QQ: 2541511219
2. 圣杯布局
反正不管是圣杯还是双飞翼,最后结果都是如上图所示。
<div class="container">
<div class="middle">
<h4>middle</h4>
<p>HHHHHHHHHHHHHHHHHHHHHH
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
HHHHHHHHHHHHHHHHHHHHHH
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
</p>
</div>
<div class="left">
<h4>left</h4>
<p>oooooooooooooo
00000000000000000
ooooooooooooooo
ooooooooooooooo
000000000000000</p>
</div>
<div class="right">
<h4>right</h4>
<p>BBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
88888888888888888888</p>
</div>
</div>
DOM上, 中间的放首位,然后是左边,右边。
操作步奏:
1.将三者都 float:left , 再加上一个position:relative (因为相对定位后面会用到)
2.middle部分 width:100%占满
3.此时middle占满了,所以要把left拉到最左边,使用margin-left:-100%
4.这时left拉回来了,但会覆盖middle内容的左端,要把middle内容拉出来,所以在外围container加上 padding:0 220px 0 200px
5.middle内容拉回来了,但left也跟着过来了,所以要还原,就对left使用相对定位 left:-200px 同理,right也要相对定位还原 right:-220px
6.到这里大概就自适应好了。如果想container高度保持一致可以给left middle right都加上min-height:130px
由于middle 宽度100%
CSS 代码:
.left,
.middle,
.right{
position: relative;
float: left;
min-height: 130px;
}
.container{
padding:0 220px 0 200px;
overflow: hidden;
}
.left{
margin-left: -100%;
left: -200px;
width: 200px;
background: red;
}
.right{
margin-left: -220px;
right: -220px;
width: 220px;
background: green;
}
.middle{
width: 100%;
background: blue;
word-break: break-all;
}
操作关键要点:
在父元素container 中通过设置 padding 值来显示left 与right。
父元素设置overflow:hidden,这个可是有讲究的,这是为了将整个块设置为BFC ,在计算BFC高度时,浮动元素也参与运算。
- 这是没开启 overflow:hidden时,可见 高度为0
- 在开启后,高度被子元素撑开。
3. 双飞翼布局
双飞翼布局是阿里提出来的,与 圣杯有以下三点区别
- 圣杯布局 需要 浮动与相对定位, 而 双飞翼 只需要浮动
- 圣杯布局 是通过父元素的 pddding 来为 left,right 预留空间, 而双飞翼 是通过 margin 来显示完整middle 的内容。
- 双飞翼 在middle中 加了一层 容器,所以Dom结构就不一样。
<div class="middle">
<div class="main-inner">
<h4>main</h4>
<p>HHHHHHHHHHHHHHHHHHHHHH
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
HHHHHHHHHHHHHHHHHHHHHH
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
</p>
</div>
</div>
<div class="sub">
<h4>sub</h4>
<p>oooooooooooooo
00000000000000000
ooooooooooooooo
ooooooooooooooo
000000000000000</p>
</div>
<div class="extra">
<h4>extra</h4>
<p>BBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBB
88888888888888888888</p>
</div>
操作步奏:
1.html代码中,middle要放最前边,sub extra
2.将middle,sub extra 都float:left
3.将middle占满 width:100%
4.此时middle占满了,所以要把sub拉到最左边,使用margin-left:-100% 同理 extra使用margin-left:-220px
(这时可以直接继续上边圣杯布局的步骤,也可以有所改动)
5.middle内容被覆盖了吧,除了使用外围的padding,还可以考虑使用margin,给main增加一个内层div-- main-inner, 然后margin:0 220px 0 200px
.sub,
.middle,
.extra{
float: left;
min-height: 130px;
}
.sub{
margin-left: -100%;
width: 200px;
background: red;
}
.extra{
margin-left: -220px;
width: 220px;
background: blue;
}
.middle{
width: 100%;
}
.main-inner{
margin-left: 200px;
margin-right: 220px;
min-height: 130px;
background: green;
word-break: break-all;
}
3. BFC 块级格式化上下文
在一个Web页面的CSS渲染中,块级格式化上下文 (Block Fromatting Context)是按照块级盒子布局的。W3C对BFC的定义如下:
浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为“visiable”的块级盒子,都会为他们的内容创建新的BFC(块级格式上下文)。
为了便于理解,我们换一种方式来重新定义BFC。一个HTML元素要创建BFC,则满足下列的任意一个或多个条件即可:
设置条件
1、float的值不是none。
2、position的值不是static或者relative。
3、display的值是inline-block、table-cell、flex、table-caption或者inline-flex
4、overflow的值不是visible
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。
怎么创建BFC
要显示的创建一个BFC是非常简单的,只要满足上述4个CSS条件之一就行。例如:
<divclass=“container”> 你的内容div>
在类container中添加类似 overflow: scroll,overflow: hidden,display: flex,float: left,或 display: table 的规则来显示创建BFC。虽然添加上述的任意一条都能创建BFC,但会有一些副作用:
1、display: table 可能引发响应性问题
2、overflow: scroll 可能产生多余的滚动条
3、float: left 将把元素移至左侧,并被其他元素环绕
4、overflow: hidden 将裁切溢出元素
因而无论什么时候需要创建BFC,都要基于自身的需要来考虑。对于本文,将采用 overflow: hidden 方式:
.container { overflow: hidden;}
再说两点
BFC中盒子怎么对齐
如前文所说,在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列。W3C给出得规范是:
在BFC中,每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)。浮动也是如此(尽管盒子里的行盒子 Line Box 可能由于浮动而变窄),除非盒子创建了一个新的BFC(在这种情况下盒子本身可能由于浮动而变窄)。
外边距折叠
常规流布局时,盒子都是垂直排列,两者之间的间距由各自的外边距所决定,但不是二者外边距之和。
<div class="container">
<p>Sibling 1 </p>
<p>Sibling 2 </p>
div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
background-color: lightgreen;
margin: 10px0;
}
渲染结果如图:
在上图中,一个红盒子(div)包含着两个兄弟元素(p),一个BFC已经创建了出来。
理论上,两个p元素之间的外边距应当是二者外边距之和(20px)但实际上却是10px,这是外边距折叠(Collapsing Margins)的结果。
在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。折叠的结果按照如下规则计算:
1、两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
2、两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
3、两个外边距一正一负时,折叠结果是两者的相加的和。
产生折叠的必备条件:margin必须是邻接的!
BFC可以做什么呢?
3.1 利用BFC避免外边距折叠
BFC可能造成外边距折叠,也可以利用它来避免这种情况。BFC产生外边距折叠要满足一个条件:两个相邻元素要处于同一个BFC中。所以,若两个相邻元素在不同的BFC中,就能避免外边距折叠。
改进前面的例子:
<div class="container">
<p>Sibling 1 </p>
<p>Sibling 2 </p>
<p>Sibling 3 </p>
</div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */
}
p {
background-color: lightgreen;
margin: 10px0;
}
结果和上面一样,由于外边距折叠,三个相邻P元素之间的垂直距离是10px,这是因为三个 p 标签都从属于同一个BFC。
修改第三个P元素,使之创建一个新的BFC:
<div class="container">
<p>Sibling 1 </p>
<p>Sibling 2 </p>
<div class="newBFC">
<p>Sibling 3 </p>
</div>
</div>
对应的CSS:
.container {
background-color: red;
overflow: hidden; /* creates a block formatting context */}
p {
margin: 10px 0;
background-color: lightgreen;
}
.newBFC {
overflow: hidden; /* creates new block formatting context */
}
现在的结果如图:
因为第二个和第三个P元素现在分属于不同的BFC,它们之间就不会发生外边距折叠了。
3.2 BFC包含浮动
浮动元素是会脱离文档流的(绝对定位元素会脱离文档流)。如果一个没有高度或者height是auto的容器的子元素是浮动元素,则该容器的高度是不会被撑开的。我们通常会利用伪元素(:after或者:before)来解决这个问题。BFC能包含浮动,也能解决容器高度不会被撑开的问题。
看个例子:
< div class="container">
<div>Sibling</div>
</div>
CSS:
.container {
background-color: green;
}
.container div {
float: left;
background-color: lightgreen;
margin: 10px;
}
在上面这个例子中,容器没有任何高度,并且它包不住浮动子元素,容器的高度并不会被撑开。为解决这个问题,可以在容器中创建一个BFC:
.container { overflow: hidden; /* creates block formatting context */}
现在容器可以包住浮动子元素,并且其高度会扩展至包住其子元素,在这个新的BFC中浮动元素又回归到页面的常规流之中了。
3.3 使用BFC避免文字环绕
如上图所示,对于浮动元素,可能会造成文字环绕的情况(Figure1),但这并不是我们想要的布局(Figure2才是想要的)。要解决这个问题,我们可以用外边距,但也可以用BFC。
First let us understand why the text wraps. For this we have to understand how the box model works when an element is floated. This is the part I left earlier while discussing the alignment in a block formatting context. Let us understand what is happening in Figure 1 in the diagram below:
假设HTML是:
<div class="container">
<div class="floated">
Floated div
<div>
<p> Quae hic ut ab perferendis sit quod architecto, dolor debitis quam rem provident aspernatur tempora expedita.
</p>
</div>
上图整个黑色区域表示 p 元素。p 元素没有移位但它叠在了浮动元素之下,而p元素的文本(行盒子)却移位了,行盒子水平变窄来给浮动元素腾出了空间。随着文本的增加,最后文本将环绕在浮动元素之下,因为那时候行盒子不再需要移位,也就成了图Figure1的样子。
再回顾一下W3C的描述:
在BFC上下文中,每个盒子的左外侧紧贴包含块的左侧(从右到左的格式里,则为盒子右外侧紧贴包含块右侧),甚至有浮动也是如此(尽管盒子里的行盒子 Line Box 可能由于浮动而变窄),除非盒子创建了一个新的BFC(在这种情况下盒子本身可能由于浮动而变窄)。
因而,如果p元素创建一个新的BFC那它就不会再紧贴包含块的左侧了。
3.4 在多列布局中使用BFC
如果我们创建一个占满整个容器宽度的多列布局,在某些浏览器中最后一列有时候会掉到下一行。这可能是因为浏览器四舍五入了列宽从而所有列的总宽度会超出容器。但如果我们在多列布局中的最后一列里创建一个新的BFC,它将总是占据其他列先占位完毕后剩下的空间。
例如:
<div class="container">
<div class="column">
column 1
</div>
<div class="column">
column 2
</div>
<div class="column">
column 3
</div>
</div>
对应的CSS:
.column {
width: 31.33%;
background-color: green;
float: left;
margin: 1%;
}
.column:last-child {
float: none;overflow: hidden;
}
现在尽管盒子的宽度稍有改变,但布局不会打破。当然,对多列布局来说这不一定是个好办法,但能避免最后一列下掉。这个问题上弹性盒或许是个更好的解决方案,但这个办法可以用来说明元素在这些环境下的行为。