答寒冬的面试题(1)

 本文也发表在我另一篇独立博客qingbob.com:  《答寒冬的面试题1》

有关注前端和微博的朋友一定留意到了前一阵寒冬和玉伯关于前端面试的讨论,后来老赵和左耳朵耗子也加入其中,讨论带来了非常多的启发和思考——不久之前自己也经历了几轮面试,决心对自己的基础开始加强,正巧寒冬老师题目由浅入深,非常有代表性,有些概念自己也模糊,决心花时间把这些概念全部弄清楚,梳理一遍

寒冬的微博原文如下

面试的时候问个css的position属性能刷掉一半的人这是啥情况……其实这问题我本来打算的是可以顺着一路扯到normal flow、containing block、bfc、margin collapse,base line,writing mode,bidi,这样一路问下去的,奈何第一个问题(亲我真的只问了position有哪些取值和行为啊)就悲剧了……

并且后来寒冬后来也就这一事件写了两篇文章(《谈谈面试与面试题》《阿里巴巴无线前端团队》)来做了一些说明,我们就根据这些材料对知识点进行一些梳理。本文将谈到:

  • position
  • normal flow
  • containing block
  • bfc
  • margin collapse
  • writing mode
  • bidi
  • 以及各种综合和边缘情况

最困难的是当你在谈一个概念的同时不得不对其他概念有所涉及——但其实所有的概念和方案都是为了解决问题而生,我打算从最简单的布局开始,以解决复杂的布局方案为线索,逐渐引入这些概念。本文的资料来源于国外国内的通俗博客和实战经验,如果直接引用w3c的概念我感觉会有一些晦涩(重要的是我看的也很头大),但仍然会做引用。本文只是抛砖引玉,如果有不正确的地方请多多指正。

 

Normal Flow

我倾向把normal flow翻译为标准流。当你在一个空白的网页上插入不同的标签div, p, span等,那么这些标签形成的元素将以何种标准进行排列?那就是标准流。

谈论标准流的前提是,页面上的所有元素都没有有关布局的css(float,display,position等)进行修饰,也就是说所有的元素要么是块状元素(block),要么是行内元素(inline)

块状元素将在页面上或者包含他们的包含块(containing block)中,从左上方开始,并且是从垂直自上往下进行填充,不用担心横向,因为块级元素非常霸道,一个元素必定占一整行,即使你给它指定宽度,它所在的那一行余下的宽度也是属于它的,不会有后面的元素进来,即使后面的元素很窄能刚好插进来。而上下两个块之间的距离则根据他们的外边距和外边距的折叠规则(margin collapse)而定,这个后面会谈到。比如看下面这个例子

div { border: 1px solid black; }
div div { height: 100px; }
.a { margin: 20px 0; }
.b { margin: 30px 0; width: 100px; }
.c { margin: -50px 0 0; }
<div>
    <div class="a"></div>
    <div class="b"></div>
    <div class="c"></div>
</div>


 

而行内元素顾名思义,则优先进行横向排列,从容器的左上开始,后面的元素一个紧挨一个的排列在这一行中,只有当位置不够了,才会被挤到下一行去,

div { width: 100px; border: 1px solid black;}
span.a {background: yellow;}
span.b {background: red;}
span.c {background: blue;}
<div>
<span class="a" >This is an span element</span>
<span class="b" >This is another span</span>
<span class="c" >This is   the third span</span>    
</div>
This is an span element This is another span This is the third span

在标准流的状态下,如果两种元素混合在一起,那么将各自遵循各自的标准,理论上是不会有干扰的。临时想到一个问题,如果块状和行内上下相邻,并且都配备有外边距,那么外边距怎么计算?再复杂一点,行内元素所在的那一行有好几个行内元素,并且每个的外边距都不相同,那么与垂直相邻的块状元素的距离怎么算?代码如下

div { width: 100px; border: 1px solid black; margin: 20px;}
span.a {margin: 10px 0 0;}
span.b {margin: 20px 0 0;}
span.c {margin: 30px 0 0;}
<div class="block"></div>
<span class="a">This is a span</span>
<span class="b">This is b span</span>
<span class="c">This is c span</span>

这个问题还涉及另一个概念:盒子模型,如果又要展开又大了去了。在这里只说一点,行内元素的margin和padding只对它的左右元素有影响,对上下无影响,具体看下面代码

.p { width: 200px; height: 50px; border: 1px solid black;}
.span {margin: 20px;}
<p>
<span>this is span</span><span class="span">this is span</span><span>this is span</span><span>this is span</span><span>this is span</span>
</p>

this is span this is span this is span this is span this is span

本节主要参考资料:

BFC(Block Formatting Context)

好吧,必须承认我也是第一接触BFC的概念,很惭愧,于是疯狂的查找资料,对它概念的介绍引用阿里巴巴用户体验部的一篇文章的一段话

什么是BFC(Block Formatting Context),简单讲,它是提供了一个独立布局的环境,每个BFC都遵守同一套布局规则。例如,在同一个BFC内,盒子会一个挨着一个的排,相邻盒子的间距是由margin决定且垂直方向的margin会重叠。而float和clear float也只对同一个BFC内的元素有效。

在提BFC的同时也不得不提另一个概念,IE的haslayout属性,一旦元素的这两个属性(haslayout在IE下)被触发,他们都能给自己提供一个独立的布局环境(我的疑问在于不独立的布局环境又是什么情况?)。当元素的CSS属性设置了下列之一时,即可创建一个BFC:

那么如何触发BFC,继续引用一淘的文章:

非块级盒子的浮动元素、绝对定位元素及块级容器(比如inline-blocks,table-cells和table-captions),以及overflow属性是visible之外任意值的块级盒子,都会创建了一个BFC。即当元素CSS属性设置了下列之一时,即可创建一个BFC:

  • float:left | right
  • position:absolute | fixed
  • display: table-cell | table-caption | inline-block
  • display: table-cell | table-caption | inline-block

然后这个独立布局的规则是这样的(摘自)

  • 在创建了 BFC的元素中,其子元素会一个接一个地放置。垂直方向上他们的起点是一个包含块的顶部,两个相邻的元素之间的垂直距离取决于 ‘margin’ 特性。在BFC中相邻的块级元素的垂直边距会折叠(collapse)。
  • 在BFC 中,每一个元素左外边与包含块的左边相接触(对于从右到左的格式化,右外边接触右边), 即使存在浮动也是如此(尽管一个元素的内容区域会由于浮动而压缩),除非这个元素也创建了一个新的BFC。

上面的定义是翻译自W3C的标准。因为自己经验有限,在这里我直接举出我找到的的BFC的经典用处,希望大家能从中体会。

在所有的文章中,被列举最频繁的例子莫过于阻止文字围绕浮动元素,请看下面代码

.item {width: 200px; height: 150px; border: 1px solid black;}
.pic{width:80px;height:80px;margin:10px;background-color:#acdae5;float:left;}
<div class="item">
	<div class="pic">picture</div>
	<p class="text">
	测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试
	</p>
</div>
picture

测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试

此时我们要做一个重要的决定!就是阻止文字围绕左侧的浮动块——那就开启右侧文字的BFC属性,用overflow:hidden

.item {width: 200px; height: 150px; border: 1px solid black;}
.pic{width:80px;height:80px;margin:10px;background-color:#acdae5;float:left;}
.text {overflow: hidden;}
picture

测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试

用法二:创建了 BFC的元素中,浮动元素参与高度的计算,这个方法也可以用于消除因为子元素浮动引起的折叠

.header {border: 1px solid black;}
.logo {width: 50px; height: 50px; background: blue; float: left;}
<div class="header">
   <div class="logo"></div>
</div>

 
 

这是一个很常见的问题,在网站有一个标题区域header,其中有一个logo,并且设置logo向左浮动。但问题是一旦logo向左浮动,它的包含块因为float的一些特性(后面介绍),自动折叠了起来

那么就设置包含块的BFC的属性吧,照上面可以使用overflow属性

.header {border: 1px solid black; overflow:hidden;}

 

于是成功了,并且此时当计算header的高度时,将把子元素的float元素的高度计算进来。其实解决这一类的问题应该算是清除浮动。这一类解决方案已经非常成熟了,并且一个好的方案知识点的信息量还是比较大的,可以好好介绍一下。

用处三:创建了 BFC的元素不会与它们的子元素发生外边距折叠

外边距折叠规则中有一条是,当赤裸裸的子元素(没有padding,border)与赤裸裸的父元素(还是没有padding,border)进行接触时,他们的外边距会按一定的规则进行折叠。比如

div {margin:30px 0; background: yellow;}
p {margin: 20px 0; background: blue;}
<div>
   <p></p>
</div>

This is text

看到上面因为父元素与子元素的外边距发生了折叠,父元素已经完全和子元素重合。如果元素是赤裸裸但又不想和父元素发生外边距折叠怎么办,那就给父元素设置BFC!

div {margin:30px 0; background: yellow; overflow: hidden;}

This is text

以上是三个基本用处的介绍,至少我个人对BFC还不是很理解,比如拥有不同布局的元素究竟应该如何在BFC的元素中排列等。希望有经验的同学指点一下。

参考文章

上面的文章几乎都拿BFC与haslayout属性进行对比,上面的第二篇把异同之处都整理了出来。如果是页面布局方向的同学可以深入阅读。

Position: relative

Position有四种可选值,static,relative,absolute,fixed。当你不对元素做任何position属性时,默认即为static,注意此时你无法对元素的z-index值进行设置,只有当你把position设置为出static以外的值时,z-index才生效。所以最保险的作法是设置相对定位。

CSS2.1中元素是根据三种的位置方案(positioning modes or schemes)被布局的,分别是标准流(normal flow), 浮动(float)和绝对定位(absolute position)

相对定位虽然被改动了position值,但是仍然遵循标准流的规则。CSS中难点之一在于,如何根据当前元素的位置属性判断出相邻,或者后代的位置属性,而判断出当前元素的位置属性并不难。

你可以这么理解,相对定位的元素仍然处于标准流中,只要你没有设置偏移值(top, left, right, buttom);如果你设置了偏移值,那么元素就会相对于你在标准流中的位置,依照值进行偏移。但你周围的元素会以为你还在标准流中,有一种灵魂出窍的感觉。见代码

div { width: 200px; height: 40px; border: 1px solid black; position: relative; }
.a {background: yellow; }
.b {background: blue; }
<div class="a"></div>
<div class="b"></div>
<div></div>
 
 
 

以上是正常情况,而下面将演示非正常情况,我将给前两个容器添加相偏移

.a {background: yellow; top: 10px; left: 10px;}
.b {background: blue; top: 10px; left: 10px;}
 
 
 

注意到虽然前两个元素虽然发生了移动,但是第三个元素并没有收到影响,并且前两个元素的移动都是根据自己的相对位移。如果这三个盒子还存在外边距的话,那么外边距的折叠效果仍然是与标准流保持一致的。

再留意上面的一个细节,这三个容器我都没有设置z-index,你也可以通过firebug之类的工具查看到它们的z-index是auto;但却出现了有叠加的情况。

元素z轴上的层叠关系不仅仅可以通过z-index这个属性来硬性规定。还可以根据浮动,负外边距,定位属性等隐式属性的隐士式体现。就上面的例子而言,当然是有position为relative的元素的优先级比static高,而同样都是relative,在代码中后书写的元素优先级又比先书写的优先级高。

z-index不在这次讲课之列,有兴趣的同学可以参考下列文章:

留意一个边界情况,如果偏移量有矛盾怎么办,比如

.a {
  left: 10px;
  right: 10px;
}

记住这样一个原则,在矛盾的情况下left的优先级要比right高,top的优先级要比bottom高,即使right书写在left后面。这一原则在绝对定位和相对于视窗定位时仍然成立。

Position: absolute

绝对定位与相对定位虽然只差一个字,但差别多了去了。首先绝对定位元素已经彻底脱离了标准流,有一种跳出三界外,不在五行中的感觉,所以它的偏移量不是相对于标准流进行偏移的,而是相对于距离它最近的position不为static的祖先元素

什么是距离它最近的position不为static的祖先元素?看下面例子

div { border: 1px solid black; }
.outer { width: 120px; height: 120px; position: relative;}
.inner { width: 80px; height: 80px; margin: 20px 0 0 20px;}
.item { width: 50px; height: 50px; background: red; 
	position: absolute; 
	left: 0; 
	top: 0;
}
<div class="outer">
    <div class="inner">
        <div class="item"></div>
    </div>
</div>

 

红色方块有两个父元素,为什么它绝对定位到离他更远的父元素?因为它的祖父元素(离它更远的那位)的position属性不为static,而它的父元素为static。如果想让它相对于父元素绝对定位怎么办,那就给父元素设置position不为static就行。

.inner { width: 80px; height: 80px; margin: 20px 0 0 20px; position:relative;}


再次强调,绝对定位就是相对于1.离它最近的父元素;2.并且这个最近父元素position值为非static。。如果元素所有的祖先元素都是static怎么办?那就相对于浏览器窗口了。

还有一个在w3c中特别注明的情况,如果父元素是行内元素怎么办?行内元素不可怕,可怕的是这个行内元素还形成了多行。看下面这个例子(你需要手动把浏览器变窄,把下面的行内元素变为多行):

.wrap { position:relative; border: 10px solid black; padding: 10px;line-height: 65px; }
.item { background: blue; position: absolute; right: 5px;bottom: 5px;} 
<span class="wrap">
    test test test test test test test test test test test test test test test test test test test test test test test test test test test
    <span class="item">This is span</span>
</span>

test test test test test test test test test test test test test test test test test test test test test test test test test test test This is span

我让另一个蓝色行内元素相对于它父元素(同样也是行内元素)进行绝对定位,定位在右下角。

注意此时多行元素的“右”是有两个不同的值,一个是第一行的右,一个是第二行的右,并且第二行的右比第一行的右还要短,那此时相对的“右”应该是哪一个右?如图所示,当然是最后一行的。

当行内元素形成多行并且左右侧不对齐时(形成了许多的line boxes),相对于它绝对定位的元素参考的区域是,以第一行inline box的左边和上边为边界(这里的边界是内容边界,content edge而非padding edge),和最后一行inline box的右边和下边为边界的矩形区域。即使中间有某几行的右侧与最后一行没有对齐,仍然以最后一行为准。所以有了上面的结果。

上面提到了一个edge概念,在盒子模型中有content edge,padding edge,border edge,margin edge。content edge内容边距指内容(content)与内边距(padding)的交界处,或者说包围内容区域的一圈边缘,另外三个同理。

于是这里我们不得不还要考虑一个问题:比如当我指定绝对定位的left偏移量时,这个量是从这个元素的哪一个边缘到相对元素的哪一个边缘?记住了,是从该元素的外边距边缘(margin edge)到相对元素的内边距边缘(padding edge)。也就是说在绝对定位中,外边距仍然是工作中的,BTW在相对定位中也是。

阿里巴巴UED有一篇文章是关于使用absolute的一个tip,可以加深对absolute的理解: 《overflow:hidden真的失效了吗》

Position: fixed

在W3C中把fixed视为absolute的一种情况,这个值可以一句话就带过了。绝对定位不是把某个祖先元素作为参考对象吗?那fixed也是把某个祖先元素视为参考对象,只不过这个参考对象是唯一的,那就是浏览器。注意,IE6中的position是没有fixed属性的

参考文章

Containing Block

大部分时候一个元素的尺寸和位置并非是由它自己决定的(比如当它没有硬性规定长宽的时候),而是要参考它所在的那个容器,或者是它需要参考的元素容器。这个容器就叫做containing block,包含块。

包含块(containing block)的定义是我觉得w3c文档中写的最通俗易懂的,大致谈了四点:

  • 根元素(我不理解为什么没有明确指出是body或html)的包含块被称为初始包含块(initial containing block),这个包含块指的是浏览器视口(viewport),就是浏览器的窗口大小。
  • 对position为relative或static的元素来说,包含块指1.最近的;2.块级祖先元素;3.内容边缘(content edge) 所形成的区域(这一条从正面来说比较好理解,但是我还没有找到这样的一个反例)
  • position为fixed的元素,包含块就是视口
  • 如果元素是绝对定位,包含块除了要满足具有出static之外的定位属性意外,还要分一下两种情况:
    • 当满足情况的祖先元素是行内元素时,包含块应该是上一节绝对定位中最后描述的,行内元素多行时,当右侧参差不齐时形成区域的规则。在CSS2.1中,如果行内元素被分为许多行,那么包含块为undefined
    • 如果不是行内元素,包含块即是那个祖先元素内边距边缘所形成的区域。
  • 最后,如果该元素都不满足上述任何条件,那么包含块即是指“初始包含块”

参考文章

Margin Collapse

关于外边距折叠,我觉得需要了解两点

  • 什么情况下会折叠
  • 什么情况下不会折叠

关于什么情况下会折叠,还要分三种情况

相邻元素的折叠:当两个或多个元素垂直排列时(标准流中),相邻的两个外边距会重合为一个外边距。这个重合外边距的值为那两个外边距的较大值;如果为一正一负的话,则为两个的和值;如果两个都是负外边距的话,最终值为负的更厉害的那个,这个就不举例了,应该很容易理解吧。

子元素与父元素之间的折叠:在之前那个BFC中提到过这个例子,当父元素与子元素赤裸裸的接触时(都没有内边距,边框;外边距的直接接触),那么父元素的外边距会与子元素的外边距产生折叠;如何组织这个折叠?触发父元素的BFC属性;具体实例的代码在那一节也都涉及到了

自己的上边距与下边距折叠:这个就有点奇葩了,当你有一个容器,设置了上下外边距,但是content area为空,那么自己的上外边距和下外边距会进行折叠!如果此时两个外边距正好一正一负,并且刚好抵消,那么你就看不到这个盒子了!

此时再如果盒子的上方来一个赤裸裸的接触,那么又触发外边距折叠,合体了!上代码

div {margin: 20px 0;}
p {margin: 30px 0;}
<div></div>
<p></p>
 

 

注意上面的空白处,用浏览器工具查看一下代码,那就是效果,因为没有任何内容,所以看起来什么都没有……

那么什么情况下不会折叠?

  • 浮动元素
  • 绝对定位元素
  • display:inline-block元素
  • 触发BFC的元素
  • 被清除浮动的元素(cleared)
  • 根(root)元素

参考资料

写了这么多感觉把,感觉已经很累了,展现拉的太长了!先最后还剩三个关于书写排版的概念,这三个就真的没有接触过了,要花一段时间消化。最后还有重头戏,关于float,以及关于float和display和position的叠加。

posted @ 2013-05-18 14:14  hh54188  阅读(1792)  评论(1编辑  收藏  举报