[关闭]
@Rico 2014-08-01T08:25:27.000000Z 字数 8207 阅读 3087

margin系列之布局篇

CSS


前端工程师对CSS的基本诉求

布局能力或许是Web前端工程师对CSS的最基本的诉求,当开始进入到这个岗位,就避免不了要和CSS打交道,而和CSS交往,布局当然是不可或缺的。
很遗憾的是,CSS2.1之前都没有出现真正意义上的布局属性,直至现如今的CSS3,才开始出现了一些,如:flex, grid 等,不过其兼容性及国内浏览器的使用情况,真令人捉急。
不过,有需求就会有变通,对于达成布局目的,已衍生出各式各样的方法,如:float, inline-block, table, absolute 等等。

margin的布局之道

其实,这个话题有点脱离 margin 的能力范围,因为单纯的 margin 并无法完成复杂布局,它更多做的是辅助,但却又难以替代。

经典左右结构

两栏结构应该是最常见和经典的网页呈现之一吧?如下 图一:
pic
相信对于这样一个网页呈现,你不会陌生。那么你有多少种方案可以达成该布局?我想,4至5种应该是保守估计吧?
这次,我们只看 margin 是如何做的。

absolute + margin 方式

HTML

  1. <header id="hd">头部</header>
  2. <div id="bd">
  3. <aside id="aside">侧边栏固定宽度</aside>
  4. <div id="main">主内容栏自适应宽度</div>
  5. </div>
  6. <footer id="ft">底部</footer>

CSS

  1. #aside{
  2. position:absolute;
  3. top:0;
  4. left:0;
  5. width:200px;
  6. }
  7. #main{
  8. margin-left:210px;
  9. }

如上关键代码,我们即可实现 图一 布局,该布局有一个特点就是,#main 可以自适应可用空间。
假定 HTML 是给定的,我们来解读一下 CSS 代码:
我们知道块级元素的特性之一是换新行,也就是说,如果想让 #main 和 #aside 在同行显示,我们要么改变其显示属性为 inline-level(即之前说的inline-block布局方式),要么改变其流方式(absolute, float, flex and etc...)。
如上述代码,我们使用了 absolute,即让 '#aside' 脱离常规流,通过绝对定位到想要的位置。
主内容栏自适应宽度
同时你会发现,我们并有改变 #main 的显示属性或者流方式,也就是说其仍然具备块级元素的特性,所以它会自动适应剩余宽度,即我们常说的自适应宽度。
我们并不希望 #main 区域会包含 #aside 在内,于是利用 margin 给 '#aside' 预留出足够其显示的空间,即可达成我们所要的布局。
可能你会问为什么是 margin-left:210px 而不是 200px,实际确实应该是 200px,多出来的 10px 只是为了创建一个列间隙,与布局实现无关。
具体代码:margin+absolute布局:左栏固定主内容自适应

  1. <title>margin+absolute布局:左栏固定主内容自适应</title>
  2. <style>
  3. <style>
  4. #demo{width:80%;margin:auto;}
  5. #hd,#ft{height:50px;background-color:#aaa;}
  6. #bd{position:relative;margin:10px 0;}
  7. #aside{position:absolute;top:0;left:0;width:200px;background:#ccc;}
  8. #main{margin-left:210px;background-color:#aaa;}
  9. </style>
  10. </style>
  11. </head>
  12. <body>
  13. <div id="demo">
  14. <header id="hd">头部</header>
  15. <div id="bd">
  16. <aside id="aside">Aside<br>侧边栏区域</aside>
  17. <div id="main">Main<br>主内容区域</div>
  18. </div>
  19. <footer id="ft">底部</footer>
  20. </div>
  21. </body>
  22. </html>

效果:
pic

就这样,是不是很简单?其实它还有亮点,那就是:
任意调整列顺序
在不修改 HTML 的情况下,只需简单的修改 CSS,我们即可让左右两栏的顺序调换
具体代码:右栏固定主内容自适应

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title>右栏固定主内容自适应</title>
  5. <style>
  6. #demo{width:80%;margin:auto;}
  7. #hd,#ft{height:50px;background-color:#aaa;}
  8. #bd{zoom:1;overflow:hidden;margin:10px 0;}
  9. #aside{float:right;width:200px;background:#ccc;}
  10. #main{margin-right:210px;background-color:#aaa;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="demo">
  15. <header id="hd">头部</header>
  16. <div id="bd">
  17. <aside id="aside">Aside<br>侧边栏区域</aside>
  18. <div id="main">Main<br>主内容区域</div>
  19. </div>
  20. <footer id="ft">底部</footer>
  21. </div>
  22. </body>
  23. </html>

效果
pic
主内容优先显示
可以更Cool一点,你觉得呢?很多时候,你也许会考虑到,不论在何种情况下,总想保证主要的内容先于次要的内容呈现给用户,那么,怎么做?
很简单,只需要将主要内容的HTML排在次要内容的HTML之前即可,因为它是顺序加载渲染的。我们可以这样:

  1. <header id="hd">头部</header>
  2. <div id="bd">
  3. <div id="main">主内容栏自适应宽度</div>
  4. <aside id="aside">侧边栏固定宽度</aside>
  5. </div>
  6. <footer id="ft">底部</footer>

是的,我们只需要将 #main 的HTML挪到 #aside 的HTML前面,令人兴奋的是,改变HTML之后,CSS不需要做任何改变
左栏固定主内容自适应,主内容优先显示

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title>左栏固定主内容自适应,主内容优先显示</title>
  5. <style>
  6. #demo{width:80%;margin:auto;}
  7. #hd,#ft{height:50px;background-color:#aaa;}
  8. #bd{position:relative;margin:10px 0;}
  9. #aside{position:absolute;top:0;left:0;width:200px;background:#ccc;}
  10. #main{margin-left:210px;background-color:#aaa;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="demo">
  15. <header id="hd">头部</header>
  16. <div id="bd">
  17. <div id="main">Main<br>主内容区域</div>
  18. <aside id="aside">Aside<br>侧边栏区域</aside>
  19. </div>
  20. <footer id="ft">底部</footer>
  21. </div>
  22. </body>
  23. </html>

效果同上,但是其实加载时main是先加载的。
致命缺陷
列举了 absolute+margin 布局的很多优点,但只说一个问题,就足以让你在是否选用这种方式时深思熟虑,是什么呢?
我们知道 absolute 是定位流,脱离正常排版,也就是说绝对定位元素不影响其上下文的排版方式,你意识到我想说什么了么?
OK,用代码来演示:

HTML

  1. <header id="hd">头部</header>
  2. <div id="bd">
  3. <div id="main">主内容栏自适应宽度</div>
  4. <aside id="aside">侧边栏固定宽度,我的内容可能比主内容多,高度比主内容栏高</aside>
  5. </div>
  6. <footer id="ft">底部</footer>

看完代码,估计你猜到了。是的,#aside 无法撑开父元素的高度,它将会溢出父元素区域,结果如下图:
pic
具体代码

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title></title>
  5. <link href="http://developer.doyoe.com/dui/lib/css/reset-grids-comm-min.css" rel="stylesheet">
  6. <link href="http://demo.doyoe.com/skin/demo.css" rel="stylesheet">
  7. <style>
  8. #demo{width:80%;margin:auto;}
  9. #hd,#ft{height:50px;background-color:#aaa;}
  10. #bd{position:relative;margin:10px 0;}
  11. #aside{position:absolute;top:0;left:0;width:200px;background:#ccc;}
  12. #main{margin-left:210px;background-color:#aaa;}
  13. </style>
  14. </head>
  15. <body>
  16. <div id="demo">
  17. <header id="hd">头部</header>
  18. <div id="bd">
  19. <div id="main">Main<br>主内容区域</div>
  20. <aside id="aside">Aside<br>侧边栏固定宽度,我的内容可能比主内容多,高度比主内容栏高域</aside>
  21. </div>
  22. <footer id="ft">底部</footer>
  23. </div>
  24. </body>
  25. </html>

此时假设你设置父元素 overflow:hidden 那么溢出部分将会被裁减,同样不符合布局意图,无法可破。所以在内容量不可控的场景,不推荐使用这种方式。

float + margin 方式

和 absolute + margin 方式一样,float + margin 方式一样是经典的利用来布局的方案,并且被更广泛使用。来看代码:

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title>margin+float布局:左栏固定主内容自适应</title>
  5. <style>
  6. #demo{width:80%;margin:auto;}
  7. #hd,#ft{height:50px;background-color:#aaa;}
  8. #bd{zoom:1;overflow:hidden;margin:10px 0;}
  9. #aside{float:left;width:200px;background:#ccc;}
  10. #main{margin-left:210px;background-color:#aaa;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="demo">
  15. <header id="hd">头部</header>
  16. <div id="bd">
  17. <aside id="aside">Aside<br>侧边栏区域我的内容可能比主内容多,高度比主内容栏高域</aside>
  18. <div id="main">Main<br>主内容区域</div>
  19. </div>
  20. <footer id="ft">底部</footer>
  21. </div>
  22. </body>
  23. </html>

如上述代码,我们使用了 float,即从图文环绕形态演变而来。当 #aside 定义了 float,那么紧随其后的元素将会环绕在其周围。不过环绕并不是我们想要的结果,我们想要的是 '#main' 也自成封闭矩形,所以利用 margin 留出足够 #aside 显示的空间,中断环绕即可。
当然,此时 #main 也是自适应宽度的
pic
它是否也具备可任意调整列顺序的特点?何不一试?
CSS

  1. #aside{
  2. float:right;
  3. width:200px;
  4. }
  5. #main{
  6. margin-right:210px;
  7. }

我们发现是支持任意调整列顺序的。
pic

从这种趋势看来,貌似 float + margin 的方式会成为黑马,不过遗憾的告诉你,这种方式无法支持主内容优先显示。但我们有更Cool的解决方案。

float + 负margin 方式

接下来我要说的大家可能都猜到了,对,经典的圣杯布局。至于圣杯的名字由来,大家可以自行Google,这里不做赘述。
恩,HTML当然是使用主内容优先显示的那种:
HTML

  1. <header id="hd">头部</header>
  2. <div id="bd">
  3. <div id="main">主内容栏自适应宽度</div>
  4. <aside id="aside">侧边栏固定宽度</aside>
  5. </div>
  6. <footer id="ft">底部</footer>

CSS

  1. #bd{
  2. padding-left:210px;
  3. }
  4. #aside{
  5. float:left;
  6. position:relative;
  7. left:-210px;
  8. width:200px;
  9. margin-left:-100%;
  10. }
  11. #main{
  12. float:left;
  13. width:100%;
  14. }

如上代码,既是圣杯布局的核心Code,如果你看懂了,你会发现,这其实很简单,不是么?
简单解释一下上面的CSS Code,首先我们是在做一个左侧固定宽度,右侧自适应宽度的布局。我们说过要让块级元素在同行显示的条件:改变显示方式,改变流方式,这里我们选择了使用 float 来将 #main#aside 变成浮动流。
OK,这时我们具备 #main#aside 能在同行显示的前置条件。我们知道,浮动元素其宽度如果没有显式定义,则由其内容决定。正好,#aside 是定宽的,所以显示给它定义 width:200px,但此时 #main 该怎么办?不设置 width 不对,因为宽度将被内容左右,设置 width:100% 也不对,因为这样的话,就没有 #aside 的立足之地了,正确的应该是 width: calc(100% - 200px),不是么?可惜,这是新特性,只好作罢。
变通?是的,有的时候稍微换个思路,你会觉得豁然开朗。
#main 不是要自适应吗?那就给它个 100%,怎么做?我们在包含块 #bd 中就将 #aside 的宽度刨除,宽度全部都给 #main。恩,我们只需要这样 #bd{padding-left:210px;} (10px仍然是用来做间隙的),这时 #main 就可以设置 width:100% 了,由于 #bd 设置了 padding,所以已在左边预留出了一块宽 210px 的区域。此时的问题在于如果将 #aside 挪到这个地方,你想对了,我们是在聊 负margin 布局,自然需要利用上。
#aside{margin-left:-100%;} 这样可以了吗?很明显,这样还不行,此时 #aside#main 的起始位置将会重合,因为 #aside 的 margin-left 计算值是相对包含块来计算的,而此时包含块的宽度等于 #main 的宽度。
如何让 #aside 再向左偏移 210px?显然 margin 是不行了,因为我们已经用掉它了。如果你看过之前的文章的话,你可能还记得,有一篇文章讲 margin系列之与相对偏移的异同。恩,是的,这时我们可以借助相对偏移。
向左偏移 210px 是件很简单的事:#aside{position:relative;left:-210px;}
至此,你的布局OK了,这就是圣杯的实现方式。
圣杯布局之左栏固定主内容自适应

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title>圣杯布局之左栏固定主内容自适应</title>
  5. <style>
  6. #demo{width:80%;margin:auto;}
  7. #hd,#ft{height:50px;background-color:#aaa;}
  8. #bd{zoom:1;overflow:hidden;margin:10px 0;padding-left:210px;}
  9. #aside{float:left;position:relative;left:-210px;width:200px;margin-left:-100%;background-color:#ccc;}
  10. #main{float:left;width:100%;background-color:#aaa;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="demo">
  15. <header id="hd">头部</header>
  16. <div id="bd">
  17. <div id="main">Main<br>主内容区域</div>
  18. <aside id="aside">Aside<br>侧边栏区域,看看能不能自适应高度</aside>
  19. </div>
  20. <footer id="ft">底部</footer>
  21. </div>
  22. </body>
  23. </html>

pic
当然,圣杯布局必须可以任意调整列顺序,要不,怎么能说是更Cool些的方案呢?CSS

  1. #bd{
  2. padding-right:210px;
  3. }
  4. #aside{
  5. float:left;
  6. position:relative;
  7. right:-210px;
  8. width:200px;
  9. margin-left:-200px;
  10. }
  11. #main{
  12. float:left;
  13. width:100%;
  14. }

这个就直接看示例好了,不再一一解释代码

  1. <html xmlns="http://www.w3.org/1999/xhtml">
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  4. <title>圣杯:右栏固定主内容自适应</title>
  5. <style>
  6. #demo{width:80%;margin:auto;}
  7. #hd,#ft{height:50px;background-color:#aaa;}
  8. #bd{zoom:1;overflow:hidden;margin:10px 0;padding-right:210px;}
  9. #aside{float:left;position:relative;right:-210px;width:200px;margin-left:-200px;background-color:#ccc;}
  10. #main{float:left;width:100%;background-color:#aaa;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="demo">
  15. <header id="hd">头部</header>
  16. <div id="bd">
  17. <div id="main">Main<br>主内容区域</div>
  18. <aside id="aside">Aside<br>侧边栏区域看看高度能不能自适应</aside>
  19. </div>
  20. <footer id="ft">底部</footer>
  21. </div>
  22. </body>
  23. </html>

pic

所以圣杯布局具备前两种方式共同的优点,同时没有他们的不足,但圣杯本身也有一些问题,在IE6/7下报废,不过不用慌,因为它可被修复。
你想到方法了吗?

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注