前端必经之路:CSS页面布局(深入理解浮动布局、定位布局、圣杯布局和双飞翼布局等重要布局方案)

    建筑师在对一栋建筑物进行施工之前,首先会根据建筑图纸上的平面图、立体图、剖面图和构造详图等对建筑物进行整体布局后再从局部施工(当然不排除有先装修完厕所再砌卧室围墙的奇葩)。在一个网页页面的搭建过程中,对于前端工程师来说也是如此。在拿到UI设计图的时候,我们需要先对页面的整体布局进行分析,再从布局开始搭建整个页面。页面布局是一个前端工程师最最基础的基本功,前端布局的方式非常多,本文将根据网站上常用的布局方案,列出各种常见的布局以及它们的实现方式。

                                  

单列布局

  单列布局常见于一些搜索引擎的主页,网页结构简洁明了,主题单一,适合排版和内容简单的网页。

  通常单列布局中盒子的宽度都是固定的,并且常用margin:0 auto;来居中显示

 

两栏布局

   两栏布局的页面由主栏和边栏构成,通常主栏的宽度自适应屏幕宽度,而边栏作为广告位固定宽度。

   两栏布局的实现方式有很多,这里只介绍一边定宽,另一边自适应宽度的情况。该情况一般有以下几种方式:

1.浮动定位 + margin实现
   元素浮动后,后面的块级元素看不见它,所以块级元素会占据它之前的位置。此时再让后面的块级元素往右或往左移动使它不与浮动元素重叠,即可让两个元素按两栏排列。

2.绝对定位 + magin实现
   给元素的position设为absolute时,元素会脱离文档流,后面的元素也会占据它的位置,此时避免重叠的方法跟第一种的原理一样。

3.float定位 + BFC



   如果不触发第二个盒子的BFC,浮动盒子会与第二个盒子重叠。在上一篇文章我曾讲过,创建了新的BFC的盒子会与浮动元素产生边界,不会与浮动元素重叠,所以也可以用这种方式实现两栏布局。
(触发BFC的具体方式在上一篇博客里有详细介绍: https://blog.csdn.net/William_bb/article/details/96877114



   用overflow:hidden 或 display:flex/table等的方法不会让元素变成行内块元素,所以自适应区域会自动铺满屏幕右侧剩余空间。
   而如果使用float或display:inline-block/table-cell等方式触发BFC,盒子会变成行内块元素,其大小根据元素里子元素的内容大小决定,所以此时应该给该元素加上百分比宽度实现自适应效果。





   如果也想让行内块元素铺满剩余空间,又害怕给第二个元素设置的宽度太大使它被挤到第二行,可以使用CSS3新增的一个功能函数calc(),自动动态计算出页面右侧剩余的空间。(使用calc()函数要注意,中间的运算符前后都要留有空格)



   但是触发BFC这里有一个特殊情况,就是不要用position的方式触发第二个盒子的BFC。区别于浮动元素不会真正脱离文档流,position:absolute或position:fixed都会使元素完全脱离文档流,所以即使它触发了BFC,也会叠在浮动元素上方。
(看来即使position触发BFC能解决margin塌陷,也不能解决与浮动元素重叠的问题)



   除非给后面的元素加上margin外边距,但这方法的原理其实就跟第一第二种方法一样,此时的position就显得多此一举了。

4.flex弹性盒子
   弹性盒子是CSS3新增的布局模式,它可以用来实现bootstrap的栅格布局。上面写到flex可以用来触发盒子的BFC与浮动元素产生边界,可其实弹性盒子的作用远不止如此。通过display:flex/inline-flex将元素定义为弹性容器后,它的子元素便成了弹性子元素,会根据弹性盒子的规则进行布局,默认在弹性盒子内一行显示。


     
     如果要让第二个盒子铺满容器container的剩余空间,可以单独将第二个盒子的放大属性flex-grow设为1。

    
   这里之所以能铺满,是因为弹性子元素的宽度满足一个计算公式,即子元素的宽度 = 本身原来的宽度 + 剩余空间的宽度 * (自身的flex-grow值/ 所有弹性子元素的flex-grow相加之和)。弹性子元素的flex-grow默认值为0,所以第一个定宽区域的宽度为 200px + 剩余的空白宽度 * (0/0+1) = 200px,第二个自适应区域的宽度为 自身宽度(这里没给它设宽度,所以为元素内容区域宽度) + 剩余的空白宽度 * (1/0+1)= 自身宽度 + 剩余的空白宽度,所以理所当然就铺满了容器这一行剩余的空间。

   弹性盒子还有很多有意思的用法,关于它的语法和使用,感兴趣的朋友可以观看阮一峰老师关于弹性盒子的这两篇文章:
Flex布局教程:语法篇
http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
Flex布局教程:实例篇 http://www.ruanyifeng.com/blog/2015/07/flex-examples.html

三栏布局
 
三栏布局是各大网站最常用的布局方式,在设计的时候通常是让左右两边的宽度固定,中间的区域自适应屏幕大小。在这里我们也只讨论这种布局情况的实现方式。


1. 左右两栏使用float定位,中间栏使用margin定位
    大多数人使用这个方法,首先想到的是给第一个和第三个元素加float,但结果会出现这种情况:


  
   这是因为第二个元素是块级元素,它会撑满一整行的空间,而浮动元素产生的浮动流是在该元素dom结构下面脱离文档流的,并不能浮到它dom结构上面的元素,所以第一个元素可以浮在第二个元素上面,第三个元素却不能浮在第二个元素上面。
   我们应该把两个浮动元素在dom结构中都放在前面,让中间元素结构放在最后面。

2.左右两栏使用绝对定位,中间栏使用margin定位
   同第一条的原理一样,这种方式也是让前两个元素脱离文档流使最后一个元素挤上来,所以它们的dom结构也要把中间元素放到最后面,只不过这里的左右定宽区域需要设置它们的left/right值为0使它们贴附在窗口的左右两侧。


3.全部使用position定位

   当然,我们也可以直接给每个元素都设置上position:absolute,这样修不修改dom结构的顺序都没有什么影响,只是中间区域的left/right值还是要像上一条的margin值那样进行设置。

4.全部使用float定位,中间栏使用calc()函数设置宽度


   前面说过浮动元素会隐式转换为行内块元素,所以这里必须主动给它设置宽度让能让它铺满屏幕两栏中间剩余宽度。

5. 全部设为行内块元素,中间栏使用calc()函数设置宽度。

   其实这个方法和第四个方法的原理基本一致,只不过有一些细微的区别:


  
    当你给中间元素宽度设置为100%减去左右两栏宽度之和时,你会发现最后一个元素被撑下去了,原因是设置了inline-block的元素具有文本属性,具有inline的元素周围的空格或换行符会被浏览器识别为占据一个字节(幽灵字符),这个空白区域的大小就是由我们设置的font-size的大小决定的。

   这个问题的解决办法,是我们可以直接将font-size设为0,或者让中间区域的宽度再缩小一些。

6.将父元素设为弹性容器(弹性盒子布局)


  
   将父元素设为弹性容器,中间的子元素通过flex-grow放大铺满剩余空白区域。这个方法文章上面已经说明过了,这里便不再重复。

7.全部使用float定位,中间栏宽度设为100%,并使用margin负值对元素进行移动。
   margin负值的功能,主要是让元素自身往左往上移动或者让自身后面的元素往左往上移动。margin负值的使用情况表面上看起来很简单,其实十分的复杂和繁琐。但它又是我们接下来要介绍的圣杯布局和双飞翼布局的实现原理,这两种布局在面试过程中经常作为考点,所以在这里还是稍微耐心的看看margin负值的使用原理吧。

(如果你只是想知道实现方式,这里关于margin负值的展开介绍可以暂时跳过)
① 当元素为块级元素时,它在水平方向和垂直方向都可以使用margin负值。

   在垂直方向上,使用margin负值的块级元素能移动覆盖到位于它dom结构之前的块级元素的上方,也能让位于它dom结构之后的块级元素移动覆盖到它上方;但垂直方向上如果遇到行内块元素、行内元素或浮动元素,不管它们之间在dom结构里的位置关系,块级元素永远都会位于它们下方。

   在水平方向上,块级元素使用margin负值,只能对自身进行左移,无法对他左侧或右侧的浮动元素产生任何影响。

(块级元素这个特性,经常被用来做居中显示。假如一个块级元素的宽度为100px,高度为80px,设置定位后,它水平居中的写法为left:50%;margin-left:-50px;垂直居中的写法为top:50%;margin-top:-40px;)

② 当元素为行内块元素或行内元素时,它们在水平方向上都可以使用margin负值,但垂直方向上只有行内块元素可以使用margin负值,并且产生的效果要视vertical-align属性而定。

   在水平方向上,它可以移动覆盖到位于它dom结构之前的行内块元素/行内元素的上方,也能让位于它dom结构之后的行内块元素/行内元素移动覆盖到它上方。

   在垂直方向上,行内块元素和行内元素无法对自身进行移动,也无法让它们下面的元素移动上来,所以垂直方向上的margin负值基本无效。行内块元素在垂直方向上的margin负值有时候可以产生影响位于它们左右两边元素的效果,但这与它自身的文本对齐属性vertical-align的设定以及左右两边元素里的文本位置有关。(这里便不举例说明了,有兴趣可以自行尝试)

③ 当元素为浮动元素时,它在水平方向和垂直方向都可以使用margin负值(这似乎与我之前讲的浮动元素会从内部将元素转成行内块元素的说法不符,但事实确是如此)

   在垂直方向上,它可以移动覆盖到位于它dom结构之前的块级元素或行内块元素上方,也能覆盖到位于它dom结构之后的块级元素或行内块元素上方(上下的效果都是浮动元素覆盖在它们上面);若位于它dom结构之前或之后的元素都为行内元素,则它始终浮动在它们的左侧或右侧;若前后都为浮动元素并因为各自宽度太大呈垂直排列,它可以覆盖到位于它dom结构之前的浮动元素的上方,也能让位于它dom结构之后的浮动元素覆盖到它上方。

   在水平方向上,浮动元素可以覆盖到位于它dom结构之前的浮动元素的上方,也能让位于它dom结构之后的浮动元素移动覆盖到它上方;若位于它dom结构之前或之后的元素都为行内块元素或行内元素,则它始终浮动在它们的左侧或右侧;若位于它左侧或右侧的元素为块级元素(该块级元素在dom结构中一定位于浮动元素之后,不可能位于该浮动元素之前),通过margin负值并不能对该块级元素本身的位置产生影响,只会影响该块级元素内的行内块元素或行内元素。

   margin负值的使用情况基本介绍完毕了,接下来就可以来看看我们实现三栏布局的第七种方式,首先我们先将三个元素设为浮动元素,然后中间的元素宽度设为100%,此时的效果如图所示:

   因为第二个元素宽度占了100%,所以它既无法浮动到第一个元素旁边,也无法让第三个元素浮动到它旁边(这里给第二个元素设置了透明度,方便接下来对元素进行观察)。

   当我们给第二个元素设置了margin-left:-200px时(200px为第一个元素的宽度),我们发现第二个元素已经跨行覆盖到第一个元素上方了。

   我们先仔细观察一下,当我们的margin-left的值还是-199px时,第二行元素还不能跨到第一行,但它自身的宽度却并不是一直铺满一整行,而是像带着100%宽度进行移动,而自身离开视口区域的199px似乎被切掉了。

     当margin-left的值达到-200px,第二行元素切掉200px后剩余的宽度恰好能挤到第一行空白区域,所以它便往第一行跑上去。这个时候按常理来理解,第二行元素剩余应该填满第一行的空白区域,并位于第一个元素右侧,可它却铺满了第一行整行区域。

   这究竟是为什么呢?其实这个地方并不难理解,当第二行元素切掉自身200px后的那一瞬间,它确实应该是位于第一个元素右侧并铺满第一行空白区域。但当第二行的元素窜到第一行后,它自身的css属性还在发挥着作用。首先它的margin-left:-200px使得它又继续往左移动了200px,同时宽度100%使它瞬间又一次撑开一整行宽度。但此时移动的200px只是占据了左浮动盒子的区域,并没有离开屏幕视口,所以还没有被切掉效果,于是我们看起来第二个元素铺满了第一行。

   于是乎,我们可以直接将第二行元素的margin-left设为-400px(即左边栏元素和右边栏元素的宽度之和),这样第三行元素也可以跟着挤到第一行来了。

   但这时候你好像发现了什么,第二行元素的内容区域变成了图中红色框区域,这样当要给中间区域里的子元素做居中布局之类的效果,就非常不理想了。我们倒不如还原原来的margin-left:-200px,然后给中间元素再加上margin-right:-200px。

   给元素设置margin-left方向的负值,我们可以很清晰的看到元素的移动情况,但给元素设置margin-right方向的负值,变化效果就没那么明显了。
 

   原先只设置margin-left:-200px时,元素并没有移出视口区域,本身刚好占据着一整行的宽度。当我们给margin-right设置负值时,我们既不能看到第二个元素移动,也不能看到第二个元素宽度减少。不过,在你给第二个元素的margin-right设置负值的时候,第二行的元素就已经跃跃欲试准备往第一行窜了,只是第一行通过负值所腾出的空间还不够容纳它。我们前面已经说过,使用margin负值时,若是水平方向上两个浮动元素相遇,前面的浮动元素往右设置margin负值时,后面的浮动元素会跟着移动过来并覆盖在前面的浮动元素上方。当我们把margin-right设到-200px时,第三个元素这时候就窜上来了。

   往右设置margin负值使其它浮动元素覆盖在自己身上,并不会对元素本身的宽高造成影响,所以第二个元素此时依然占满第一行整个区域。

   这样实现三栏布局的方式,中间区域里子元素的居中等其它效果就不会受影响了。

   不过,我们是不是忘了一件事,第二个元素还覆盖在第一个元素上面呢!

   我们去掉第二个元素的透明度,第一个元素便忘记了曾答应过的不会让我们把它找不见…


  
   这个时候我们就要想办法让第一个元素浮在第二个元素上面,我们首先想到的是利用css中的堆叠属性z-index来改变元素与元素之间的堆叠顺序。
 

 

  
    z-index只能在定位元素上奏效,所以要利用堆叠属性,必须先对元素进行定位。为了不影响后面其它元素的布局,我们在这里使用相对定位。当我们给元素定位后,元素就有了自己默认的堆叠属性。不同的浏览器默认的堆叠属性值不同,但一般都为0或auto。有了默认z-index值的盒子,根据七阶层叠水平规则,会显示在浮动盒子上方。所以我们这里直接给第一个元素设置相对定位,让它的默认堆叠属性生效,就可以显示在第二个浮动盒子上面了。
 

   关于z-index具体的规则,这里就不展开讨论了,感兴趣的朋友可以参考一下这篇关于z-index属性介绍的文章
https://blog.csdn.net/u012207345/article/details/82744505
 

8.圣杯布局
   花了那么长的时间讲第七种方法,现在终于可以介绍这个著名的圣杯布局了。圣杯布局的实现原理和第七种方式没有什么不同,都是运用到了浮动和margin负值,中间宽度设置100%。但在实现方式上其实还是有很大的区别,主要在于圣杯布局dom结构的排列顺序以及中间元素所占的区域。

                              

   在有些情况下,例如网速慢等问题,页面加载可能不太流畅。为了能使我们的主体内容能在dom页面的加载过程中优先展示,我们经常运用到圣杯布局的布局方式——把主体内容区域放在dom结构的首位。不过为了能展示三栏布局的效果,我们需要先给父盒子加上内边距padding值。

   给父元素加上内边距后,容器里面的左右两边就空出来了,左右空出来的宽度正好是左右两栏的宽度,接下来我们就要想办法让左右两个浮动元素跑上去。

   我们给左栏元素设置向左的margin负值,由于还没有移出视口,所以左浮动元素会在第二行先往左一直挪。
 

   待到挪了200px后,左栏就会一下子窜到第一行的内容区域(padding里面的content区)。

  

    如果我们给左栏元素设置margin:-100% 是什么效果呢?

 

   会看到它直接窜到第一行content区域里的最左边,其实这个地方并不难理解,这里的100%宽度是页面总宽度减去padding区域的content区,大小正好等于第一行蓝色区域的宽度。一开始在第二行向左移动的200px,再加上左浮动元素窜到第一行最左边后剩余的蓝色区域的宽度,加起来正好等于第一行content区域100%宽度,所以就直接挪到蓝色区域最左边了。有点不好解释,如果看不懂就在自己电脑上琢磨一下吧)

   而我们想要的效果,是左栏填满第一行的空白区域,所以我们这时候给左栏设置相对定位,再用left将左栏往左移动自身宽度就可以了。

   这样我们就把左栏完整的放置到左边空白区域了,同样的方法,我们给右栏设置margin-left:-200px,让它先窜到第一行蓝色区域最右边。

   然后再给它设置相对定位,用right往右移动自身宽度。

   这个就是传说中的圣杯布局啦,区别于我们接下来要讲的双飞翼布局,圣杯布局中间的自适应区域因为父盒子padding的原因并没有挤到左右两栏的位置。

   为了在我们调整浏览器窗口大小时页面能正常显示,我们还应该给页面设置一个最小宽度(包括上述所有有定宽的方法都应该给页面设置最小宽度),除了左右两栏加起来的400px,还有它们在中间区域使用相对定位位移保留下来的200px(相对定位是相对自己进行移动,元素会保留原来的位置),所以页面最小宽度要设为600px。

9.双飞翼布局

   双飞翼布局是由淘宝UED团队提出的概念,它的布局结构和圣杯布局类似,但在自适应区域多了一层嵌套结构。

   一开始的步骤和圣杯布局很像,因为父元素没有设置padding值,所以这里少了用相对定位移动左右元素的步骤。

   因为没有给嵌套子元素设置宽度,此时嵌套子元素的宽度随父元素middle撑开到一整行。此时我们给中间嵌套子元素加上外边距,它的宽度就只占中间区域了。

   好了,说完了,这就是双飞翼布局,是不是非常简单!? 由于双飞翼布局没有用到相对定位,所以页面最小宽度只需设为左右两栏的宽度之和。

   比较圣杯布局与双飞翼布局我们可以发现,圣杯布局在DOM结构上显得更加直观和简洁,而双飞翼布局在实现上由于不需要使用定位,所以css代码量更少,且允许的页面最小宽度通常比圣杯布局更小。

10.table样式布局
   在一个元素上设置了display:table,就创建了一个表格容器。而给子元素设置上display:table-cell后,子元素就会作为表格的单元格显示(类似<td>和<th>)。表格布局的特点在于你不需要给中间元素设置100%宽度,但需要给父容器设置100%宽度。我们可以给左右两栏设置定宽,中间区域便会自动撑开剩余的空间。

   table样式布局的兼容性不好,并不经常使用,所以在这里便不做过多的展开。

11.网格布局

   网格布局是CSS有史以来最强大的布局方案,当你在一个元素上设置了display: grid或者display:inline-grid,就创建了一个网格容器,它下面的直接子元素都会成为网格元素。

    网格容器可以使用grid-template-columns 来定义每一列的宽度,每一个参数对应每一个子元素的宽度(如果参数超出子元素个数会创建空白网格区域)。这里使用了网格布局提供的fr单位(fraction 的缩写,意为"片段"),与flex-grow的使用方式类似,表示占据剩余空白区域的多少,由于这里只有一个元素使用了fr,所以它的1fr将铺满剩余空白区域。

   网格布局配合弹性盒子,使用起来非常的方便,极大程度上改善了我们的布局方式。网格布局在不断的发展完善,现在各大浏览器都陆续支持网格布局,日后它必将成为页面重要的布局方式。想深入了解网页布局的朋友,可以观看这篇关于CSS Grid布局的指南:https://www.html.cn/archives/8510/#prop-grid-column-row

       页面布局的方式多种多样,除了以上介绍的常用情况,还可以有其它解决方案,但相信这篇文章介绍的方案已足以满足你日常的开发需求了。

   早期的网页布局实现的主要手段,是采用html的table标签(并不是上文提到的css中的table样式布局)。当时的网页构成相对比较简单,多以文本和静态图片组成,类似于报纸多板块的形式。但随着网页要求的提高和技术的不断更新迭代,用table标签布局就暴露出了它的缺点:标签结构繁杂,网页的编写有时需要嵌套多重表格才能实现,相互嵌套使得代码复杂度高,且不利于搜索引擎抓取信息,直接影响到网站的排名。因此table标签现在已不再作为布局工具了,现在对它的使用仅用以呈现表格化数据。

十年前使用表格布局的网站

   div+css的布局方式,符合W3C标准,代码结构清晰明了,结构与样式、行为分离,具有较好的可维护性。table标签布局加载网页时,必须等整体加载完毕再呈现内容;而div+css布局是边加载边显示的,加快了页面的加载速度,节省了站点所占的空间和流量,结构化的html提高了搜索引擎对网页的搜索效率。但div+css也有它的缺点,它对于开发技术的要求更高,开发人员要考虑到各个版本浏览器的兼容情况,且分离出的css如果在加载过程中出现异常,将使页面的显示效果十分不堪。但不管怎么说,div+css的方式依然是目前最好的布局方式。


写点生活感悟:

   要说这个时代什么职业赚钱最快,答案莫过于网红了。最近网红乔碧罗的形象颠覆了大众对网红底线的认知,为何网红人设一个个的崩塌,看到一篇文章给出了答案:很多网红的学历并不高,小小年纪就辍学混社会,他们本身的德行、学识、修养,跟他们所拥有的流量、名气、金钱、荣誉是不匹配的。当这样的人进入一个快速发展的行业,借着风口迅速获得名气和金钱后,难免得意忘形,最终反噬自己。中国有十四亿人口,扣除老人儿童,只要左脚一出家门,就有八亿人想发财。想创业想年薪百万的人很多,但愿意沉下心来做事的人很少。当别人打牌游戏睡觉玩乐你都照跟,又凭什么能超越人家呢?这个时代缺乏沉淀,许多人忘了生活本来的样子,能用心在做事的人越来越少了。世界上唯一的弯路就是试图走捷径,而那些真正能走得远的人,无一不是有自己的实力在兜底。

  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值