Open TanXinNiao opened 3 years ago
多栏布局有三种基本的实现方案:固定宽度、流动、弹性 固定宽度布局的大小不会随用户调整浏览器窗口大小而变化,一般是 900到 1100 像素宽。其中 960 像素是最常见的,因为这个宽度适合所有现代显示器,而且能 够被 16、12、10、8、6、5、4和 3整除,不仅容易计算等宽分栏的数量,而且计 算结果也能得到没有小数的像素数。
流动布局的大小会随用户调整浏览器窗口大小而变化。这种布局能够更好地适应 大屏幕,但同时也意味着放弃对页面某些方面的控制,比如随着页面宽度变化, 文本行的长度和页面元素之间的位置关系都可能变化。Amazon.com的页面采用的 就是流动中栏布局,在各栏宽度加大时通过为内容元素周围添加空白来保持内容 居中,而且现在的导航条会在布局变窄到某个宽度时收缩进一个下拉菜单中,从 而为内容腾出空间。
今天,越来越多的浏览器都支持 CSS媒体查询了。这就让基于浏览器窗口宽度提 供不同的 CSS样式成为可能。在这种形势下,适应各种屏幕宽度的可变固定布局, 正逐步取代流动布局。这种可变的固定布局能够适应最大和最小的屏幕,业界称 之为响应式设计。本书第 8章将专门介绍响应式设计相关的 CSS技术。
弹性布局与流动布局类似,在浏览器窗口变宽时,不仅布局变宽,而且所有内容 元素的大小也会变化,让人产生一种所有东西都变大了的感觉。到目前为止,我 还没见过设计得非常好的弹性布局,主要是因为它太过复杂了。本章不介绍这种 布局,而只把笔墨花在固定宽度布局和流动布局上。
布局高度 多数情况下,布局中结构化元素(乃至任何元素)的高度是不必设定的。事实上, 我甚至想告诉你根本不应该给元素设定高度。除非你确实需要这样做,比如在页面 中创造一个绝对定位的元素。
为什么正常情况下都应该保持元素 height 属性的默认值 auto 不变呢?很简单,只有 这样元素才能随自己包含内容的增加而在垂直方向上扩展。这样扩展的元素会把下 方的元素向下推,而布局也能随着内容数量的增减而垂直伸缩。假如你明确设定了 元素的高度,那么超出的内容要么被剪掉,要么会跑到容器之外——取决于元素 overflow 属性的设定。
布局宽度 与高度不同,我们需要更精细地控制布局宽度,以便随着浏览器窗口宽度的合理变 化,布局能够作出适当的调整,确保文本行不会过长或过短。如果随意给元素添加 内边距、边框,或者元素本身过大,导致浮动元素的宽度超过包含元素的布局宽度, 那浮动元素就可能“躲”到其他元素下方。
就从一个简单的居中的单栏固定布局开始吧
<div id="wrapper">
<article>
<! -- 这里是一些文本元素 -->
</article>
</div>
布局相关的 CSS如下:
#wrapper {width:960px; margin:0 auto; border:1px solid;}
article {background:#ffed53;}
通过给整个外包装设定宽度值,并将其水平外边距设定为 auto ,这 个单栏布局在页面上居中了。随着向里添加内容,这一栏的高度会相应增加。外包 装中的 article 元素本质上就是一个没有宽度的块级盒子(关于“没有宽度的盒子”, 请参见 3.2 节),它水平扩展填满了外包装。下面,我们再向外包装里添加一个导航 元素,让它作为第二栏。
<div id="wrapper">
<nav>
<!-- 无序列表 -->
</nav>
<article>
<! -- 文本 -->
</article>
</div>
我们得浮动作为两栏的容器元素,让它们并排显示
#wrapper {width:960px; margin:0 auto; border:1px solid;}
nav {
width:150px;
float:left;
}
nav li {
/*去掉列表项目符号*/
list-style-type:none;
}
article {
width:810px;
float:left;
width:810px;
float:left;
background:#ffed53;
}
把两栏容器元素的总宽度设定为外包装的宽度(150 + 810 = 960), 并浮动它们,就可以创造出并肩排列的两栏来。每一栏的长度取决于内容多少。采 用同样的方法,可以添加第三栏(或任意多个栏)。
<div id="wrapper">
<nav>
<!-- 无序列表 -->
</nav>
<article>
<! -- 文本 -->
</article>
<aside>
<! -- 文本 -->
</aside>
</div>
#wrapper {width:960px; margin:0 auto; border:1px solid;}
nav {
width:150px;
float:left;
background:#dcd9c0;
}
nav li {
list-style-type:none;
}
article {
width:600px;
float:left;
background:#ffed53;
}
aside {
width:210px;
float:left;
background:#3f7ccf;
width:210px;
float:left;
background:#3f7ccf;
}
通过把三个浮动容器的总宽度设定为恰好等于外包装的宽度(150 + 600 + 210 = 960),就有了三栏布局的框架。就用这种办法,我可以想加多少栏就加 多少栏,只要它们的总宽度等于外包装的宽度即可。当然,多栏布局通常都有与布 局同宽的页眉和页脚,下面我们就来添加。
<div id="wrapper">
<header>
<!-- 标题 -->
</header>
<nav>
<!-- 无序列表 -->
</nav>
<article>
<! -- 文本 -->
</article>
<aside>
<! -- 文本 -->
</aside>
<footer>
<!-- 文本 -->
</footer>
</div>
此时的页眉与布局同宽,其内容高度也比较合适。但是,页脚位于浮动元素后面,
所以就会尽量往上移动。解决这个问题也很简单
footer {clear:both;}
只要一调整各栏中的内容,布局就可能超过容器宽度,而右边的栏就可能滑到左边 的栏下方。
为了让内容与栏边界空开距离,为栏添加水平外边距和内边距,或者为了增加栏 间距,为栏添加外边距(只要开始给布局添加样式,就一定会采用这里说的一种 做法,甚至双管齐下),导致布局宽度增大,进而浮动栏下滑。换句话说,右边浮 动的栏因为没有足够的空间与其他栏并列,就会滑到左边栏的下方。
在栏中添加大图片,或者没有空格的长字符串(如长 URL),也会导致栏宽超过布 局宽度。同样,这种情况下右边的栏也会滑到左边的栏下方。
我们也有三种方法来预防该问题发生。
从设定的元素宽度中减去添加的水平外边距、边框和内边距的宽度和。
在容器内部的元素上添加内边距或外边距。
使用 CSS3 的 box-sizing 属性切换盒子缩放方式,比如 section {box-sizing: border-box;} 。 应用 box-sizing 属性后,给 section 添加边框和内边距都不会增 大盒子,相反会导致内容变窄。
我们分别试验一下这几种手段
1. 重设宽度以抵消内边距和边框 比如我们给 600像素宽的栏又添加了 20 像素的内边距,为了抵消增加的内边距,可 以把栏宽减少 40像素而设定为 560像素。这样,右边的栏就能归位。问题在于,每 次只要调整内、外边距就要重设布局宽度,有点烦人。因此这个办法虽然可行,却 不够理想。说不定哪一回调整内边距或边框就会导致布局错乱。
2. 给容器内部的元素应用内边距和边框 把外边距和内边距应用到内容元素上确实奏效。前提是这些元素没有明确地设定宽 度,这样它们的内容才会随着内、外边距的增加而缩小。就像盒模型定义所说的, 没有宽度的元素在水平方向上会适应其父元素,其内容会随着外边距、边框和内边 距的增加而减少。
然而,一栏之中可能会包含大量不同内容的元素。假如将来又决定调整内容与容器 边界的距离,就必须每个元素都要进行调整,这样不仅麻烦,而且容易出错。况且, 给栏添加边框同样会增大栏宽,不可能通过为其包含的内容元素逐个应用样式来 做到。
所以说,与其为容器中的元素添加外边距,不如在栏中再添加一个没有宽度的 div , 让它包含所有内容元素,然后再给这个 div 应用边框和内边距。如此一来,只要为 内部 div 设定一次样式,就可以把让所有内容元素与栏边界保持一致的距离。而且, 将来再需要调整时也会很方便。任何新增内容元素的宽度都由这个内部 div 决定。
关于表现性标记的思考
HTML 的目的是语义,也就是给内容赋予含义。而 CSS 呢,是为了把表现性的样式分离出来才
发明的。不过,有些表现性标记是有害的,而有些则没有副作用。使用表格来创建多栏布局,
或者使用
标签在段间换行,却不使用
标签,这种做法的确不值得提倡,因为这会造 成内容难以移植。比如说吧,用三个表格单元作为三栏,这种布局到哪都会显示成表格,就算 是在完全不合适的智能手机里也一样。如果表现性标记无法用 CSS 修改,或者在 CSS 不可用时 也要迫使用户接受,那就是滥用 HTML。可是, div 或 span 这种中性的元素,对默认样式没 有影响,除非你给它们应用样式,否则它们就跟不存在一样。所以,我认为添加这种元素达到 表现性的目的是完全可以接受的。
下面我就为大家示范怎么用内部 div 上边存在的问题
<article>
<div class="inner">
<!-- 文本 -->
</div>
</article>
现在,把造成问题的内边距从栏上去掉。为了示范这个技术有多好用,接下来我们 不仅要给内部 div 应用内边距,还要给它应用外边距和边框。
article {
width:6oopx;
float:left;
padding:10px 20px;
background:#ffed53;
}
article .inner {
margin:10px;
border:2px solid red;
padding:20px;
}
给没有宽度的内部 div 应用外边距、边框和内边距后,中间栏的宽度没有变化,右边的 栏仍然还在原来的位置上
中间栏的宽度并未因此有什么变化,因为内容区减少的宽度抵消了应 用到内部 div 上的外边距、边框和内边距的总宽度。总之,由此可以得出一个结论: 如果布局中的栏是浮动的,而且都设定了宽度,你就根本不要去动它!要动,就把 内容放在内部 div 里,动这个 div 。
好了,解决问题的关键已经讲清楚了。接下来我们去掉中间栏的外边距、边框和内 边距,给其他两栏也添加内部 div ,然后只给这三栏加上内边距。
<div id="wrapper">
<header>
<!-- header text -->
</header>
<nav>
<div class="inner">
<ul>
<!-- 链接 -->
</ul>
</div>
</nav>
<article>
<div class="inner">
<!-- 文本 -->
</div>
</article>
<aside>
<div class="inner">
<!-- 文本 -->
</div>
</aside>
<footer>
<!-- 文本 -->
</footer>
</div>
接下来我们就利用这个 div 为三个栏中的内容创造间距。此外,还居中了页脚中的 内容。
nav .inner {padding:10px;}
article .inner {padding:10px 20px;}
aside .inner {padding:10px;}
footer {text-align:center}
子-星选择符 所谓“子-星选择符”就是一个组合选择符,利用它可以不使用内部 div 就能设定一栏中所有元素的外边距。 星号选择符可以选择“所有元素”,故而,在一个选择符后面加个星号,比如 someSelector 就可以选择 someSelector 所代表元素的所有后代元素。子选择符可以选择“某元素的子元素”,故而,把子选择符放到星号前面,比如 someSelector > 就会只选择 someSelector 所代表元素的所有子元素,而非后代元素。这正好适用于选择容器内部的所有顶部元素,然后设定它们的外边距。比如,对于 section 栏,设定 section > * {margin:0 10px;} ,就 能为栏中所有子元素,不包括其他后代元素,各应用 10 像素的左、右外边距。 使用“子-星选择符”要注意两点。第一,在为子元素设定垂直外边距时,只能使用 margin-top 和 margin-bottom ,不能使用简写的 margin ,否则会抵消用“子-星选择符”应用给这些元素的水平外边距。如果你想进一步缩进某个子元素的内容,就应该给该子元素应用内边距。 第二,“子-星选择符”有潜在性能问题,因为它会导致浏览器遍历整个 DOM 结构去查找所有匹配的元素。但我也发现这一点性能影响几乎可以忽略不计。假如你的页面真的包含几千上万个元素,那倒确实该考虑用 ySlow 或其他性能度量工具测一测这个选择符的影响。
使用 box-sizing:border-box 这 是最 简 单 的 一 个 办 法 。 只 要 在 三 个 浮 动 的 栏 的 CSS 规 则 中 分 别 加 上 box-sizing:border-box 声明,再给栏添加内边距(和边框)就不会导致盒子的宽度 变化了。
<div id="wrapper">
<header>
<!-- 标题 -->
</header>
<nav>
<ul>
<!-- 链接 -->
</ul>
</nav>
<article>
<!-- 文本 -->
</article>
<aside>
<!-- 文本 -->
</aside>
<footer>
<!-- 文本 -->
</footer>
</div>
* {margin:0; padding:0;}
#wrapper {width:960px; margin:0 auto; border:1px solid #000;
overflow:hidden;}
header {background:#f00;}
nav {
box-sizing:border-box;
width:150px;
float:left;
background:#dcd9c0;
padding:10px 10px;
}
/*去掉列表项目符号*/
nav li {list-style-type:none;}
article {
box-sizing:border-box;
width:600px;
float:left;
background:#ffed53;
padding:10px 20px;
}
aside {
box-sizing:border-box;
width:210px;
float:left;
background:#3f7ccf;
padding:10px 10px;
}
footer {clear:both; background:#000;}
/去掉列表项目符号/ nav li {list-style-type:none;} article { box-sizing:border-box; width:600px; float:left; background:#ffed53; padding:10px 20px; } aside { box-sizing:border-box; width:210px; float:left; background:#3f7ccf; padding:10px 10px; } footer {clear:both; background:#000;}
<body>
<!-- HTML 标记 -->
<!-- 只让 IE8 之前的 IE 加载它 -->
<!--[if lt IE 8 ]>
<script src="helpers/borderBoxModel.js"></script>
<![endif]-->
<!--[if lt IE 8 ]>
<script src="helpers/borderBoxModel.js"></script>
<![endif]-->
</body>
预防过大的元素 设计一个将来可能由他人维护的动态网站时,需要考虑得更长远一些。比如,应该预见到可能 出现一些过大的元素。如果将来有一张比浮动栏更宽的图片被放到栏中,就会导致布局变宽, 而右边的栏又会滑到下方。为此,一个简单的预防措施就是添加一条.inner img {max-width:100%;}声明,以便限制图片的宽度不超过其父元素(在此就是内部 div)。 另一个办法是给每个栏(或者内部 div,如果你用了的话)添加 overflow:hidden 声明。这条 声明不会缩小图片以适应父元素,而会将它(以及任何过大元素)超出容器边界的部分剪切掉。动态网站中另一个潜在的问题是换行。HTML 只会在单词间空格的地方换行。一些长 URL,甚 至一些长单词,在栏比较窄的情况下,都会导致栏宽过大。因此,还应该给所有栏的外包装元 素应用 word-wrap:break-word 声明,以便所有栏及其内容继承这个设定。有了这条声明,浏 览器会把过长的词断开显示在不同行上。只是 word-wrap 没有定义在哪里断开,因此结果完全 是随机的,而且没有连字符。不过,这条规则只在需要时才会起作用,而且能保护布局不会被 长 URL 顶得支离破碎。建议你在每一栏中都用长 URL、大图片,以及包含内容过多的元素测试 一下布局,看看这些声明到底会不会起作用,并发现更多需要事先考虑保护措施的其他 漏洞。
实现中栏流动布局有两种方法。一种是在中栏改变大小时使用负外边距定位右栏, 另一种是使用 CSS3 让栏容器具有类似表格单元的行为。负外边距适合比较老的浏览 器,而 CSS的 table 属性则要简单得多。本节介绍这两种方法。
实现三栏布局且让中栏内容区流动(不固定)的核心问题,就是处理右栏的定位, 并在中栏内容区大小改变时控制右栏与布局的关系。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
* {margin:0; padding:0;}
body {font:1em helvetica, arial, sans-serif;}
div#main_wrapper{
min-width:600px; max-width:1100px;
/*超过最大宽度时,居中布局*/
margin:0 auto;
/*背景图片默认从左上角开始拼接*/
background:url(images/bg_tile_150pxw.png) repeat-y #eee;
}
header {
padding:5px 10px;
background:#3f7ccf;
}
div#threecolwrap {
/*浮动强制它包围浮动的栏*/
float:left;
width:100%;
/*背景图片右对齐*/
background:url(images/bg_tile_210pxw.png) top right repeat-y;
}
div#twocolwrap {
background:url(images/bg_tile_210pxw.png) top right repeat-y;
}
div#twocolwrap {
/*浮动强制它包围浮动的栏*/
float:left;
width:100%;
/*把右栏拉到区块外边距腾出的位置上*/
margin-right:-210px;
}
nav {
float:left;
width:150px;
background:#f00;
padding:20px 0;
}
/*让子元素与栏边界保持一定距离*/
nav > * {margin:0 10px;}
article {
width:auto;
margin-left:150px;
/*在流动居中的栏右侧腾出空间*/
margin-right:210px;
background:#eee;
padding:20px 0;
}
article > * {margin:0 20px;}
aside {
float:left;
width:210px;
background:#ffed53;
padding:20px 0;
}
aside > * {margin:0 10px;}
footer {
clear:both;
width:100%;
text-align:center;
background:#000;
}
</style>
</head>
<body>
<div id="main_wrapper">
<header>
<!-- 页眉-->
</header>
<div id="threecolwrap">
<div id="twocolwrap">
<nav>
<!-- 导航 -->
</nav>
<article>
<!-- 区块 -->
</article>
</div>
<aside>
<!-- 侧栏 -->
</aside>
</div>
<footer>
<!-- 页脚 -->
</footer>
</div>
</body>
</html>
下面简单说明其原理。三栏中的右栏是 210 像素宽。为了给右栏腾出空间,中栏 article 元素有一个 210像素的右外边距。当然, 光有这个外边距只能把右栏再向右推 210 像素。别急,包围左栏和中栏的两栏外包 装上 210像素的负右外边距,会把右栏拉回 article 元素右外边距(在两栏外包装内 部右侧)创造的空间内。中栏 aticle 元素的宽度是 auto (原文 100%有错误。—— 译者注),因此它仍然会力求占据浮动左栏剩余的所有空间。可是,一方面它自己的 右外边距在两栏外包装内为右栏腾出了空间,另一方面两栏外包装的负右外边距又 把右栏拉到了该空间内。
人造栏技术 有人可能会纳闷,这些栏怎么都跟布局一样高呢?实话跟你说吧,你看到的都是假 象!这里我采用了一种叫“人造栏”的技术,这样才让所有栏看起来都一样高了。 这个技术说来也简单,就是给包围栏的外包装元素应用与各栏同宽的背景图片和背 景色。外包装元素跟它们包含的栏不一样,它们的高度就是布局高度,当然与内容 区的高度相同。通过在它们的背景的垂直方向上重复拼接背景图片,就可以在视觉 上造成各栏与布局同高的假象。
左栏的背景图片加在了 div#main_wrapper 上。右栏的 背景图片加在了 div#threecolwrap 上,而且让它沿该 div 的右侧垂直拼接。中栏的 背景色也加在了 div#main_wrapper 上,这个背景色实际上是整个布局的背景色。而 位于两侧的两栏的背景图片,以及页眉和页脚的背景色,都会覆盖这个背景色(子 元素覆盖父元素)。因此,只能在中栏看到该全局背景色。
在最简单的情况下,表格由三个元素构成。一个表格外包装
,比如下面这个例子。
```
TanXinNiao
commented
3 years ago
多行多栏布局前面的布局中只包含一个 article 、一个 nav ……,因此比较容易选择,只要用标签 名即可。而在创建复杂布局时,一个页面中会出现多个相同的标签,选择的时候就 要用上下文选择符来区分它们了。我想通过下面的例子,告诉你怎么给标记中添加 最少的 ID和类,同时精确地选择任意元素 CSS选择符的实际应用随着页面变得越来越复杂,相同的 HTML元素(如 section 、 article 、 nav ,等等) 会出现很多次——比如,前面布局中的 article 就出现了 7次。为了选择某个元素, 必须区分这些相同的标签名。为此,有些新手会给每个标签都添加一个不同的类名。 但这种做法是不值得提倡的。不仅因为类本身就不该这么用(类应该用于标记具有 相同特征的元素),而且这么多类会把标记弄得很乱,让 CSS也很难看懂。为了知道 每个类代表哪个元素,你必须不断地查看 HTML。 更好的做法是给标记中每个主要区域的顶级元素添加一个 ID,这也是使用 ID的正确 方式,ID 就是标识页面中唯一元素用的。然后,这些 ID 就会成为 HTML 标记中的 “路标”,放在上下文选择符开头的时候,它们就能起到框定后代元素的作用。这就 是在标记中保持类和 ID属性最少的秘诀。而且,相应的上下文选择符也能清晰地传 达出路径信息,让人从 CSS中一眼就能看出它要选择哪个元素。
上边的代码中,我用 section#feature_area article 选择了三个盒子,用一条规则声明了这些元素共 有的样式,包括宽度、内边距、边框等。这样,对这三个盒子只需维护一组声明即可。 然后,通过三条规则分别为三个盒子设定独特的样式。下面就是为第二个内部 div 应用样式的规则:
从后往前,这条规则的意思是:选择类为 inner 的元素,它必须是父元素中的第二 个 article ,而且这个 article 必须包含在 ID 为 feature_area 的 section 元素中, 将它的边框设定为淡橙色 显然,只要顶级元素上有一个 ID,我们就可以把它作为“路标”,进而选择它的任意 后代元素(甚至能够选择将来才会加入其中的内容元素)。另外,同样重要的是,这 样也避免了无意中把样式添加给标记中的其他元素上。如果在没有别的手段让你选 择某个特定元素的情况下,只在标记中添加一个类或一个 ID,那么 HTML就能保持 清晰整洁,而 CSS也将易读易维护。
TanXinNiao
commented
3 years ago
内部DIV实战上边的布局非常恰当地演示了内部 div 承担的定位和样式这两个角色。具体来说, 这个布局中的内部 div 不仅确保了水平内边距不会破坏布局,另外也承担了一个视 觉功能——内容区的彩色边框就是它们的边框 再强调一次吧,每一栏中的间距要依靠内部 div 。这个例子中的水平间距是由内部 div 的左、右外边距生成的,它们把这个 div 压缩了一下,这才使内容远离了父元素 article (如果此时直接给 article 元素应用水平内边距的话,一定会破坏布局)。而 每一栏中的垂直间距是由父元素的内边距生成的。为什么要用父元素呢?原因前面 也提到过,就是在父元素没有上、下边框的情况下,子元素的上、下外边距会折 叠的。 |
这一章会介绍实现多栏布局的几种方法。主要介绍“用内部 DIV创建浮动的栏”,这 是多年来一直被业界用于创建多栏布局的经典技术。另外,随着支持 CSS3 的浏览器 比例越来越高,一些新的布局技术也可以用在大型站点中了,本章当然不会忽略这 部分内容。今天,我们不仅可以使用 box-sizing 属性代替内部 DIV,也可以使用让 元素行为跟表格一样的 CSS3 的 display 属性(却不必向 HTML 中添加表格)。 因此, 创建流动的全长栏也变得简单多了。