相对简单的 3 等高列 CSS 流动布局






4.85/5 (48投票s)
本文尝试解释一种新的方法来解决使用纯 CSS 和 (X)HTML 实现三列(甚至更多列)等高流式布局的令人烦恼的问题。我自己的解决方案避免了伪列和超级内边距技巧(以及这些方法引起的麻烦)。

目录
简介
序言
这是创建 (X)HTML CSS 布局时最令人恼火的问题之一,所有 Web 开发人员都知道:三列**等高**布局。到目前为止最好的解决方案是伪列方法(如果您不喜欢我的解决方案,仍然是),它使用背景图像来模拟列。问题是我正在做一个应该尽可能“灵活”的大项目,而伪列方法不是灵活性的最佳示例。
我的解决方案试图在满足灵活性的同时,允许使用 2 到 3、4 甚至更多等高列,并且背景颜色仅通过 background-color
CSS 属性指定。
注意:我不得不更改一些文章样式,使其与 Code Project 网站配合使用,因为该网站不符合 XHTML 标准,但我将示例中的代码保留了其在 XHTML 页面中的样子,因为此解决方案仅用于 XHTML,并且我不支持 HTML 请求(例如“这在我的 HTML 3.0 网站上不起作用”等)。
优点和缺点
像所有解决方案一样,我的也有优点和缺点。一如既往,这完全取决于你得到什么以及失去什么。
优点
- 非常灵活的流式布局,允许您使用混合尺寸(例如,像素固定的左列和百分比固定的右列)
- 弹性中心列,可在需要时调整大小
- 无伪列(无需图片)
- 仅使用 XHTML 和 CSS,无脚本和条件注释
- 适用于所有现代浏览器(IE 6/7、Firefox 2、Mozilla 1.7、Opera 9.1、Safari 1.3.2、NN 8.1.3 部分 - 请参阅备注、NN 9)
缺点
- 标记有点重,使用了额外的
div
- CSS 也有点重,使用了 3 个 hack 和 2 个修复
- [更新:已在 NN 9 中修复] Netscape 在类似 Firefox 的行为时存在一个未解决的问题(请参阅备注)
- 可能还有其他
方法
相对和溢出
在 HTML 中实现三列垂直对齐且中心列具有弹性的功能非常简单。只需将三个 div
彼此相邻并对其进行 float
即可。然而,要使这些列看起来高度相同,我们必须使它们的背景看起来高度相同。HTML 语言的一个关键特性是每个容器(主要指每个元素)都有一个默认的透明背景,从而有可能拥有(理论上)无限数量的重叠层,后面的层具有与第一个相同的背景(如果未设置后续的 background-color
)。
因此,三列等高的问题就归结为以某种方式**获得一个分离成 3 个等高条纹的视图**,我们在其上放置实际包含列的 3 个 div。
在考虑实现这种“三条纹视图”的方法时,我开始思考我首先需要什么:那就是一种让 HTML 元素高度相同的方法,无论哪个更高。众所周知,这可以通过在块级元素中使用嵌套来实现。所以,需要做的是将 div
s 各自嵌套在内部,并移动它们以获得我们的三条纹“视图”。然后,如我所说,我们可以放置我们真正的(即物理的)列。这可以通过 CSS 属性 position: relative
(允许我们移动子 div
s)和 overflow: hidden
(允许我们隐藏子 div
s 中否则会溢出父级空间的那些部分)来实现。
让我们看看整个过程是如何工作的
这里有一些示例,详细地一步一步解释了关键概念是如何工作的。每个示例包含 2 个 div
,一个带有蓝色背景(div.parent-box
),另一个带有黄色背景(div.child-box
)。这些 div
s 代表了我们稍后将用于创建页面宽度列的内容。蓝色 div
是**容器**框,而黄色 div
是**被包含**的框。在示例框旁边有一个表格,显示了分别设置在元素上的 CSS 属性。我只把最重要的元素放在里面,删除了只为让事物看起来更清晰而添加的元素(例如 border
)。
第一个是空示例。也就是说,是两个简单的父子 div
第二个示例介绍了相对性的概念
下面的示例展示了当父级的 overflow 属性设置为 overflow: hidden
时会发生什么。正如您所见,子框在其父框轮廓结束的地方被截断了
更多相对和溢出
现在我们已经了解了基本概念的工作原理,让我们通过使用 3 个 div(最终将成为我们的页面宽度列)来使其变得复杂一些。
如前所述,我们可以通过将父背景颜色设置为我们想要的条纹颜色,然后将子框移动等于条纹/列所需宽度的量来创建“条纹”(即列背景)。
但是,在添加 3 个条纹时,当我们移动第一个子元素时,第二个子元素显然也会移动(我们可以称这个第二个子元素为“孙子”,因为它是父元素的子元素的子元素)。由于我们必须从右边移动孙子(以创建右条纹),我们需要让孙子的左边距覆盖父级的右边距(我们应该称之为祖父吗?)。这样,我们就可以将右边的相对偏移设置为我们想要的右侧列的宽度。
使这些边距重叠的最佳方法是重置子元素的定位,即使左边距重叠,然后添加 100% 的 margin-left
。我们将通过在子元素和孙子元素之间添加一个额外的容器来实现这一点,并将其应用于这个新容器,该容器将被称为 reset-box
。在此之后,孙子的左边距将覆盖“祖父”的右边距。现在我们要做的就是将孙子右移到我们想要的右侧列的宽度。
要做的最后一件事是调整内容的位置。事实上,所有内容现在都位于孙子框所在的位置。只需应用与之前相同的方法,我们在孙子元素和内容之间添加一个额外的容器,称之为 content-box
。然后,我们将它向左相对移动右侧列的量,并将其 left-margin
设置为 -100%(注意负号),从而使其左边距覆盖原始父容器(祖父)的左边距。
下面的示例显示了上述推测的结果。左列选择的宽度为**40px**,右列宽度为**25%**。仔细查看标记和新样式,以了解上述宽度值去向以及整个过程如何工作
下面的示例显示了与示例 4 相同的示例,但父蓝色背景框的 CSS 属性 overflow 设置为 overflow: visible
,从而使包含的框完全可见。此外,我添加了一些边距使事物更清晰。
下面的示例显示了与示例 4 相同的示例,但父蓝色背景框的宽度设置为更大的值:width: 400px
。请注意,更改布局的宽度只需要更改主容器的单个 CSS 值。另请注意,由于我们为右侧列选择了固定的 25% 宽度,因此它的大小也相应地改变了。
放置两列
现在我们已经了解了关键概念是如何工作的,让我们用两列做一个第一个示例。它的结构将与前三个两列示例大致相同,但此外,其内容将被浮动以创建两列的物理结构。
从现在开始,我们将更改我们将用于引用容器 div
的名称(和 CSS 类)。更改的原因是,从现在开始我们将讨论列(包括物理列),因此条纹 div
s(我们用于创建列背景的 div
s)将被称为它们所属的列的背景,除了包含所有内容的、主容器,它将奇怪地被称为 main-box
。
下面的示例显示了一个两列的示例。请注意,一如既往,为了清晰起见,我没有在 CSS 代码表格单元格中设置任何边框样式,尽管 main-box
容器 div
有一个深红色实线边框
边框在此
如前所述,添加边框时,一切都会变得更加棘手。虽然添加外部边框没有变化——您只需在 main-box
容器中设置相应的边框(border-left
用于左列的左边框,top-border
用于顶部边框,依此类推)——但尝试在列之间添加边框时会出问题。
原因是,当您在容器中放置边框时,容器的子边框不会覆盖父级边框。相反,它将被放在**旁边**。因此,当我们相对移动子 div
s 时,它们的位置将进一步移动等于先前边框大小的数量。
下面的示例与示例 5 大致相同(我也使用了相同的文本内容),但增加了两列之间的边框。这可以通过设置 right-box
容器的 border-left
CSS 属性来实现。我放了一个大边框(10px)来突出问题
正如您所见,布局已经向右移动了与边框宽度相同的距离。因此,我们唯一需要做的就是将整个布局的最终位置从右侧重置,其距离与上面提到的左边框宽度(10px)相同。此外,我们需要在 right
div
中设置一个相同尺寸的 padding-left
,以避免后者覆盖边框。
下面的示例与示例 5.1 大致相同,但增加了上述两个更改,以调整带有边框的布局
三者
现在已经讨论了所有关键概念,我们可以介绍我们第一个包含臭名昭著的三列的完整示例。
下面的示例显示了我们最终的、3 列等高的结构。从下面的标记代码可以看出,right-box
已将其名称更改为 center-box
,因为它成为了我们布局的中心(插入了新的绿色背景的右列)。除了更改名称,CSS 样式保持不变。另外请注意,下面的示例(示例 6)中,左列的宽度设置为**25%**,右列设置为**40px**,而中心列则填充剩余空间
以下内容与示例 6 相同,只是 main-box
容器更宽。我增加了它的宽度以显示布局的灵活性以及它如何适应它所放置的空间。我将相同的文本内容保留在列中以突出差异:
正如您所见,由于增加了整个布局的宽度,左列的宽度也相应增加,因为它被设置为**25%**;相反,右列的宽度保持不变,因为它被固定为**40px**。此外,正如您所能清楚看到的,中心列(它是弹性的)填充了剩余的空间,并且由于水平空间更多,它的高度减小了,因此不再是最高的列。事实上,现在最高的列是右边的列,正如您所看到的,所有背景的高度都相同。
代码
标记
我在缺点部分已经提到,标记包含比通常多 3-4 个 div
(确切数量取决于您通常的布局风格),这可能对某些人来说是不可接受的。作为一种缓和,我可以说是 div
的数量(可能)可以通过处理边框和重置位置所需的容器来减少(也因为边框)。但是,对我来说,这 3 个额外的 div
并不是大问题,我努力寻找一种方法来减少它们的数量。
这是 (X)HTML 标记
<div class="main-box">
<div class="center-box">
<div class="reset-box">
<div class="right-box">
<div class="content-box clearfix">
<div class="left">
</div>
<div class="right">
</div>
<div class="center heightfix">
</div>
</div>
</div>
</div>
</div>
</div>
样式
下面是用于工作布局的干净且可直接使用的 CSS 代码。尽管代码可能看起来棘手,但在仔细阅读之后,如果您理解了所使用的方法(在第 2 部分“方法”中解释),我认为很容易实现您需要更改的内容以拥有自己的布局。例如,如果您想增加列的宽度或更改背景颜色等。
所以,这是 CSS 代码
div.main-box {
position: relative;
width: 500px;
overflow: hidden;
background: #D0E1E1;
border: solid 1px #993333;
}
div.center-box {
position: relative;
width: 100%;
left: 25%;
background: #FFFFCC;
border-left: solid 1px #993333;
}
div.reset-box {
position: relative;
width: 100%;
left: 100%;
margin-left: -25%;
}
div.right-box {
position: relative;
width: 100%;
margin-left: -40px;
background: #D0EEC0;
border-left: solid 1px #993333;
left: -2px;
}
div.content-box {
position: relative;
width: 100%;
margin-left: -100%;
left: 40px;
}
div.left {
float:left;
width:25%;
}
div.right {
float: right;
width: 40px;
text-align: center;
text-transform: uppercase;
}
div.center {
margin-left: 25%;
margin-right: 40px;
padding-left: 1px;
padding-right: 1px;
}
* html div.center {
height:1%;
margin:0;
}
* html div.left {
margin-right:-3px;
}
* html div.right {
margin-left:-3px;
}
.clearfix:after {
content: ".";
display: block;
height: 0px;
clear: both;
visibility: hidden;
}
.heightfix:before {
content: '.';
display: block;
visibility: hidden;
height: 0;
}
示例
三列为空
下一个是包含三个列以及页眉和页脚的完整示例。里面没有内容,只有一些 <br⁄>
使布局在高度上增长。这里的宽度是左列**25%**,右列**120px**,而整个布局宽度是 80%。
三列和内容
有关更多详细信息/信息,请参阅以下示例。
三列、内容和边框
以下是一个现实世界的假设示例布局。我添加了内容和样式,使其看起来像一个一般的(丑陋的?)网站。在下一个代码表中,我删除了实际内容,并用占位符替换它们以节省空间。如果您想查看内容以获取更多信息,请查看工作示例的源代码。此外,我还从样式代码中删除了所有与三列布局需求不直接相关的样式。您将在下一个表格中看到的所有 CSS 代码都包含在一个名为threecol.css的即用型 CSS 文件中,您可以从表格本身的底部链接中获取。正如我所说,此文件已准备好在您自己的页面中使用,并且还包含详细的描述(在 CSS 注释中),解释了哪些值用于什么目的(再次),以便您可以轻松地更改它们以拥有自己的自定义布局。
四列
在下面的示例中,我将尝试使用我的方法来创建具有 4 列的布局。尽管可能不是那么有用(至少不像 3 列那样),但这种布局有助于更好地学习整个方法的工作原理。
五列?
最后一个示例仅用于教学目的。我认为没有人会需要这样的东西。
备注
布局问题
一如既往,此解决方案也有问题。到目前为止,我没有发现任何问题,除了 Netscape Navigator 的问题。我发现了该浏览器总共 3 个问题,并解决了其中前两个。第三个问题仍然未解决,但我实际上认为由于问题的特殊性,也有解决方案。
Netscape 问题 -- 已在 NN 9 中修复
如上所述,在启用了“显示方式与 Firefox 相同”选项的**Netscape Browser**(v 8.1.3)中,当整个布局的高度低于显示区域时,#main-box
容器会在右侧显示 1px 的背景。解决方案是简单地为 html
标签设置 overflow: scroll
。您可以在所有 3 列示例中使用的style.css文件中看到这一点。
此外,还有一个小问题(仍然是 Netscape,但以“显示方式与 IE 相同”选项使用),我找到了解决方案。当您在 #left-box
、#center-box
或 #right-box
列中使用**命名锚点**时,会出现此问题。也就是说,当您单击指向命名锚点的链接时,浏览器会跳回被单击的锚点。要解决此问题,只需为命名锚点设置 tabindex
属性,值为 0,如我在上面提供的实际示例中所示。
未解决的问题也与命名锚点有关。此行为与超级内边距的相似,除了仅出现在 Netscape 上且痛苦较小。实际上,当页面中有命名锚点时,单击指向命名锚点的链接**可以**正常工作。问题仅在您按刷新按钮时出现(例如,在 URL 中选择命名锚点,如 *http://somesite.com/somefile.html#mylink*)。相反,如果您按 URL 旁边的“GO”按钮,浏览器会正确显示页面。我知道这有点奇怪,但幸运的是,由于其奇怪性,它是可以忽略的。
注意:上述问题已灰色显示,因为它们已在 Netscape Navigator 的新版本(9.0 版)中得到解决,因此我认为它们现在不再重要。
比我能的更多的列
如果您真正理解了我的方法,那么猜测如何添加更多列应该不难。尽管我认为没有人会需要超过 4 或 5 列(我已经提供了示例),但您可能想尝试一个复杂的布局,例如 10 列,以惊叹于该方法的灵活性或用于教学目的。
正如您在阅读文章时应该学到的,布局的基本概念是相对地移动和取消移动(重置)位置。因此,当您进行更改(容器的移动)时,您需要在某个地方有一个重置该更改的方法(也就是说,取消移动)。因此,理论上,您需要两个**值**(margin-left
和 left
),它们允许您移动两个不同的容器(而不是同一个容器,否则您将完全隐藏更改)。一个容器用于添加列,另一个附加的**透明**(不覆盖布局的颜色)容器用于调整布局位置。总之,如果移除中心列(它不是真正的列,只是一个填充物),实际的列就需要一个值来表示它们本身,以及另一个值在另一个容器中来恢复位置。此外,您还需要一个额外的值来重置左边框占用的空间,您可以将其放在最后一个容器中,如果有多于 1 个右列,则还需要一个值来处理右边框。
我做了一个计算,可能是不正确的,以获得在布局超过 3 列时除常规容器之外所需的容器数量。正如我所说,我确定它是错误的/不精确的,但它给出了一个关于给定数量的列需要多少容器的一般想法:您需要将实际列的总数(即布局列数减 1,中心列)加倍,然后加 1。例如,对于 4 列布局,您需要 ((4-1)*2)+1 = 7 个容器;对于 5 列布局,您需要 ((5-1)*2)+1 = 9 个容器,依此类推。