CSS布局教程(从经典方法到最新技术)

本文概述

  • 用例
  • 使用经典CSS2技术的CSS教程
  • 利用CSS3
  • 总结
在不掌握CSS的情况下掌握网络布局与学习在旱地游泳一样可行。但是, 与游泳不同(一旦掌握了游泳, 这是一种终生难忘的技能), 掌握CSS是一个永无休止的过程, 因为CSS本身一直在不断发展。
不同浏览器(甚至同一浏览器的不同版本)在CSS实施和支持方面的差异以及CSS建议的采用率不同, 使挑战更加严峻。十多年来, Web设计人员和开发人员一直在努力应对每个新浏览器版本所支持的零星和不一致的突发CSS3功能。
但是, 无论如何, 精通CSS对于任何扎实的Web设计人员或开发人员都是绝对必要的。本文将引导你了解一些基本的CSS布局原理, 从经典的CSS2技术到CSS3中的最新布局方法。
注意:本文中的所有代码示例均使用HTML5元素和Sass语法。可以从https://github.com/laureanoarcanio/css-layout-examples克隆完整的工作代码。
用例 学习技术的最好方法之一是拥有你要支持的特定用例或你要解决的特定问题。为此, 我们将重点关注具有特定要求的用例。
我们的用例包括具有某些动态行为的Web App布局。它在页面上将具有固定的元素, 例如页眉, 页脚, 导航菜单和子导航, 以及可滚动的内容部分。以下是具体的布局要求:
  • 基本布局
    • 页眉, 页脚, 导航菜单和子导航都固定在滚动条上
    • 导航和子导航元素占据所有垂直自由空间
    • 内容部分使用页面上所有剩余的可用空间, 并具有可滚动区域
  • 动态行为
    • 导航菜单默认情况下仅显示图标, 但也可以展开以同时显示文本(然后可以折叠以再次仅显示图标)
  • 布局变化
    • 有些页面的导航菜单旁边有子导航, 有些页面没有
使用经典CSS2技术的CSS教程
CSS布局教程(从经典方法到最新技术)

文章图片
首先, 这是我们将在使用经典CSS的示例实现中使用的HTML5标记:
< body class="layout-classic"> < header id="header"> < /header> < nav id="nav"> < /nav> < aside id="subnav"> < /aside> < main id="main"> < /main> < footer id="footer"> < /footer> < /body>

固定位置
在CSS2中, 我们可以通过采用使用固定位置的定位布局模型来实现页面上的固定元素(页眉, 页脚等)。
另外, 我们将使用z-index CSS属性来确保固定元素保持在页面其他内容的” 顶部” 。 z-index属性指定元素的堆栈顺序, 其中具有较高堆栈顺序的元素始终位于具有较低堆栈顺序的元素的” 顶部” 。请注意, z-index属性仅适用于定位的元素。对于我们的示例, 我们将任意使用z-index值20(高于默认值), 以确保固定元素在视觉上保持在最前列。
另外, 我们将width属性设置为100%, 指示浏览器水平使用元素的所有可用空间。
#header, #footer { position: fixed; width: 100%; z-index: 20; }#header { top: 0; height: 5em; }#footer { bottom: 0; height: 3em; }

好, 那就是页眉和页脚。但是#nav和#subnav呢?
CSS扩展
对于#nav和#subnav, 我们将使用一种稍微复杂一点的技术, 称为CSS扩展, 可以在将元素定位为固定(即, 在页面上的固定位置)或绝对元素(即, 将元素定位在相对于其最接近的祖先或包含块的指定位置)。
通过将元素的顶部和底部属性都设置为固定值来实现垂直扩展, 因此元素将垂直扩展以相应地使用剩余的垂直空间。基本上, 你正在做的是将元素的顶部绑到距页面顶部的特定距离, 并将元素的底部绑到距页面底部的特定距离, 因此元素会扩展为填充整个垂直空间在这两点之间。
同样, 通过将元素的左右属性都设置为固定值来实现水平扩展, 因此元素将水平扩展以相应地使用剩余的水平空间。
对于我们的用例, 我们需要使用垂直扩展。
#nav, #subnav { position: fixed; top: 6em; /* leave 1em margin below header */ bottom: 4em; /* leave 1em margin above footer */ z-index: 20; }#nav { left: 0; width: 5em; }#subnav { left: 6em; /* leave 1em margin to right of nav */ width: 13em; }

默认(静态)定位
可滚动内容的主要区域可以仅依靠默认(静态)位置, 由此元素按照在文档流中出现的顺序进行渲染。由于页面上的所有其他位置均处于固定位置, 因此该元素是文档流中唯一的元素。结果, 我们需要对其进行正确定位的所有操作是指定其margin属性, 以避免与固定的页眉, 页脚和nav / subnav重叠:
#main { margin: 6em 0 4em 20em; }

至此, 我们已经满足了使用CSS2的用例的基本布局要求, 但是我们仍然需要满足动态功能的其他要求。
使用经典CSS2技术的动态行为
要求指出, 默认情况下, 我们的” 导航” 菜单将仅显示图标, 但也可以展开以显示文本(然后可以折叠以再次仅显示图标)。
CSS布局教程(从经典方法到最新技术)

文章图片
首先, 只需在展开时将5em添加到导航菜单的宽度中即可。为此, 我们将创建一个” 扩展的” CSS类, 该类可以动态添加到导航菜单元素中或从中删除:
#nav { left: 0; width: 5em; & .expanded {/* Sass notation */ width: 10em; } }

这是一个JavaScript代码示例(在本示例中, 我们使用jQuery), 该代码可根据用户单击导航切换图标来在展开和折叠模式之间动态切换导航菜单:
$('.layout-classic #nav').on('click', 'li.nav-toggle', function() { $('#nav’').toggleClass('expanded'); });

这样, 我们的导航菜单现在可以动态扩展或折叠。大。
好吧, 虽然很棒, 但并不完全。尽管导航菜单现在可以展开和收缩, 但在页面的其余部分中效果不佳。扩展的导航菜单现在与子导航重叠, 这绝对不是所需的行为。
这揭示了CSS2的主要局限性之一。也就是说, 有太多方法需要使用固定位置值进行硬编码。因此, 对于页面上需要重新定位以容纳扩展的导航菜单的其他元素, 我们需要使用更多固定的位置值定义其他” 扩展的” CSS类。
#subnav { left: 6em; width: 13em; & .expanded { left: 11em; /* move it on over */ } }#main { margin: 6em 0 4em 20; z-index: 10; & .expanded { margin-left: 25em; /* move it on over */ } }

然后, 当用户单击导航切换时, 我们还需要扩展JavaScript代码以添加对这些其他元素的动态调整:
$('.layout-classic #nav').on('click', 'li.nav-toggle', function() { $('#nav, #subnav, #main').toggleClass('expanded'); });

好的, 效果更好。
使用经典CSS2技术的布局变化
现在, 让我们解决使某些页面隐藏子导航菜单的需求。具体来说, 我们希望在用户单击主导航区域中的” 用户” 图标时隐藏子导航菜单。
CSS布局教程(从经典方法到最新技术)

文章图片
因此, 首先, 我们将创建一个适用于展示广告的新” 隐藏” 类:无:
.hidden { display: none; }

再次, 当用户单击用户图标时, 我们将使用JavaScript(jQuery)将” 隐藏” CSS类应用于#subnav元素:
$('#nav.fa-user').on('click', function() { $('#subnav').toggleClass('hidden'); });

使用此添加项后, 当用户单击” 用户” 图标时, #subnav元素将正确隐藏, 但它已占用的空间仍未使用, 而不是其他元素扩展为使用#subnav元素腾出的空间。
【CSS布局教程(从经典方法到最新技术)】为了在隐藏#subnav元素时获得理想的行为, 我们将使用一种鲜为人知但非常有用的CSS选择器(称为相邻兄弟选择器)。
相邻的同级CSS选择器
相邻的同级选择器允许你指定两个元素, 仅选择紧随指定的第一个元素之后的第二个元素的实例。
例如, 以下内容将仅选择ID为main的那些紧随ID为subnav的元素的元素:
#subnav + #main { margin-left: 20em; }

上面的CSS代码段仅在紧随显示的#subnav之后, 才将#main的左边距设置为20em。
但是, 如果#nav被扩展(根据我们之前的代码, 扩展的类也被添加到#main中), 我们会将#main的左边距移至25em。
#subnav + #main.expanded { margin-left: 25em; }

并且, 如果#subnav被隐藏, 我们将#main的左边界一直移到6em处, 紧靠#nav:
#subnav.hidden + #main { margin-left: 6em; }

(注意:使用相邻的同级选择器的一个缺点是, 它迫使我们始终在DOM中存在#subnav, 无论是否显示它。)
最后, 如果#subnav被隐藏并且#nav被扩展, 我们将#main的左边距设置为11em:
#subnav.hidden + #main.expanded { margin-left: 11em; }

这使我们无需任何繁琐的JavaScript代码就能将所有内容连接在一起, 但是我们还可以看到, 如果在页面中添加更多元素, 此代码将变得多么复杂。我们再次看到, 使用CSS2, 需要大量对位置值进行硬编码才能使事情正常进行。
利用CSS3 CSS3提供了显着增强的功能和布局技术, 使其更易于使用, 并且对硬编码值的依赖性大大降低。 CSS3本质上是为支持更多动态行为而设计的, 因此从某种意义上讲它是” 可编程的” 。让我们研究其中一些与我们的用例相关的新功能。
CSS3 calc()函数
新的CSS3 calc()函数可用于动态计算CSS属性值(不过请注意, 支持因浏览器而异)。提供给calc()函数的表达式可以是使用标准运算符优先级规则组合基本算术运算符(+, -, *, /)的任何简单表达式。
calc()函数的使用可以帮助避免CSS2所需值的许多硬编码。就我们而言, 这使我们能够更动态地实现CSS扩展。例如:
#nav, #subnav { position: fixed; height: calc(100% - 10em); /* replaces */ z-index: 20; }

通过上面使用calc()函数的高度规范, 我们获得了与CSS2中top:6em和bottom:4em相同的结果, 但是以更加灵活和自适应的方式, 并且无需对顶部和底部的位置进行硬编码价值观。
CSS3 Flexbox布局
Flexbox是CSS3中引入的新布局(支持因浏览器而异)。使用flexbox布局, 可以更轻松地在页面上排列元素, 从而可以在不同的屏幕尺寸, 分辨率和设备上正常运行。因此, 它在响应式Web设计的上下文中特别有用。
主要功能包括:
  • 定位子元素要容易得多, 并且可以使用更简洁的代码更简单地实现复杂的布局。
  • 子元素可以在任何方向上布置, 并且可以具有灵活的尺寸以适应显示空间。
  • 子元素会自动扩展合同以适应可用的可用空间。
Flexbox引入了自己独特的术语和概念集。其中一些包括:
  • Flex容器。其显示属性设置为flex或inline-flex的元素, 用作flex项目的容器元素。
  • 弹性项目。 flex容器中的任何元素。 (注意:直接包含在flex容器中的文本被包装在匿名flex项目中。)
  • 轴每个flexbox布局都有一个flex方向, 该flex-方向指定用于放置flex项目的主轴。交叉轴则是垂直于主轴的轴。
  • 线。根据flex-wrap属性, 可以将弹性项目布置在单行或多行上。
  • 尺寸高度和宽度的flexbox等效项是main尺寸和cross size, 它们分别指定flex容器的主轴和交叉轴的尺寸。
好的, 因此, 通过这个简短的介绍, 下面是使用Flexbox布局时可以使用的替代标记:
< body class="layout-flexbox"> < header id="header"> < /header> < div class="content-area"> < nav id="nav"> < /nav> < aside id="subnav"> < /aside> < main id="main"> < /main> < /div> < footer id="footer"> < /footer> < /body>

对于我们的示例用例, 我们的主要布局(页眉, 内容, 页脚)是垂直的, 因此我们将flexbox设置为使用列布局:
.layout-flexbox { display: flex; flex-direction: column; }

尽管我们的主要布局是垂直的, 但是内容区域中的元素(导航, 子导航, 主要)是水平放置的。每个flex容器只能定义一个方向(即, 其布局必须为水平或垂直)。因此, 当布局需要的更多时(一种常见的情况是应用布局), 我们可以将多个容器相互嵌套, 每个容器具有不同的方向布局。
这就是为什么我们添加了一个额外的容器(我称为内容区域)来包装#nav, #subnav和#main。这样, 整个布局可以垂直放置, 而内容区域的内容可以水平放置。
现在, 为了放置flex项目, 我们将使用flex的简写属性flex:< flex-grow> < flex-shrink> < flex-basis> ; 。这三个flex属性决定了我们的flex项目如何沿流向分配它们之间剩余的任何自由空间, 如下所示:
  • flex-grow:指定一个项目相对于同一容器内其余柔性项目可以增长多少
  • flex-shrink:指定项目如何相对于同一容器内的其余柔性项目收缩
  • flex-basis:指定项目的初始尺寸(即收缩或增长之前的尺寸)
CSS布局教程(从经典方法到最新技术)

文章图片
将flex-grow和flex-shrink都设置为零意味着项目的大小是固定的, 并且不会因容纳或多或少的可用空间而增长或收缩。这是我们对页眉和页脚进行的操作, 因为它们的大小是固定的:
#header { flex: 0 0 5em; }#footer { flex: 0 0 3em; }

要使项目占用所有可用空间, 请将其flex-grow和flex-shrink值都设置为1, 并将flex-basis值设置为auto。这是我们对内容区域所做的, 因为我们希望它占用所有可用的空闲空间。
如前所述, 我们希望内容区域内的项目沿行方向布置, 因此我们将添加display:flex; 和flex-direction:row; 。这使内容区域成为#nav, #subnav和`#main的新伸缩容器。
因此, 这就是内容区域CSS的最终结果:
.content-area { display: flex; flex-direction: row; flex: 1 1 auto; /* take up all available space */ margin: 1em 0; min-height: 0; /* fixes FF issue with minimum height */ }

在内容区域中, #nav和#subnav都具有固定的大小, 因此我们仅相应地设置flex属性:
#nav { flex: 0 0 5em; margin-right: 1em; overflow-y: auto; }#subnav { flex: 0 0 13em; overflow-y: auto; margin-right: 1em; }

(请注意, 我在这些CSS规范中添加了overflow-y:隐藏功能, 以克服超出容器高度并使容器高度溢出的内容。Chrome实际上不需要, 但FireFox不需要。)
#main将占用剩余的可用空间:
#main { flex: 1 1 auto; overflow-y: auto; }

一切看起来都很不错, 因此现在让我们添加动态行为, 看看情况如何。
JavaScript与以前的JavaScript相同(除了此处, 我们指定的CSS元素容器类为layout-flexbox, 而之前为classclass):
$('.layout-flexbox #nav’).on('click', 'li.nav-toggle', function() { $('#nav').toggleClass('expanded'); });

我们将扩展类添加到CSS如下:
#nav { flex: 0 0 5em; /* collapsed size */ margin-right: 1em; overflow-y: auto; & .expanded { flex: 0 0 10em; /* expanded size */ } }

瞧!
请注意, 这一次我们不需要让其他项目知道宽度变化, 因为flexbox布局可以为我们处理所有这些变化。
剩下的唯一一件事就是隐藏子导航。你猜怎么着?使用与以前相同的JavaScript代码, 这也” 有效” , 而无需进行任何其他更改。 Flexbox知道可用空间, 它自动使我们的布局工作而无需额外的代码。很酷
Flexbox还提供了一些有趣的方式来使垂直和水平元素居中。我们在这里认识到表示语言包括自由空间的概念是多么重要, 以及使用这些技术可以使代码变得可扩展。另一方面, 这里的概念和符号要比经典CSS掌握更多。
CSS3网格布局
如果Flexbox布局位于CSS3的最前沿, 则可以说Grid Layout处于其前沿。 W3C规范仍处于草拟状态, 并且对浏览器的支持仍然有限。 (通过chrome:// flags中的” 实验性Web平台功能” 标志在Chrome中启用该功能)。
就是说, 我个人不认为该草案具有革命性。相反, 正如HTML5设计原则所指出的那样:” 当一种做法在作者中已经很普遍时, 请考虑采用它, 而不是禁止它或发明新东西。”
因此, 基于标记的网格已经使用了很长的时间, 因此现在CSS网格布局实际上就是遵循相同的范例, 提供了其所有优点, 并且在表示层中提供了更多功能, 而无需标记。
一般的想法是要有一个预定义的, 固定的或灵活的网格布局, 我们可以在其中放置元素。像flexbox一样, 它也遵循自由空间原理, 并且允许我们在同一元素中定义垂直和水平” 方向” , 这在代码大小和灵活性方面带来了优势。
网格布局引入了两种类型的网格:即显式和隐式。为简单起见, 我们将重点关注显式网格。
像flexbox一样, Grid布局引入了自己独特的术语和概念集。其中一些包括:
  • 网格容器。一种元素, 其显示属性设置为” 网格” 或” 内联网格” , 通过定位并对齐到预定义的网格(显式模式), 将包含的元素布置到其中。网格是水平和垂直网格线的交集, 将网格容器的空间划分为网格单元。有两套网格线。一个用于定义列, 另一个与它正交以定义行。
  • 网格轨迹。两条相邻的网格线之间的间隔。每个网格轨道都分配有一个大小调整功能, 该功能控制列或行的宽度或高度, 以及边界网格线的距离。
  • 网格单元。两个相邻的行和两个相邻的列网格线之间的空间。它是放置网格项目时可以引用的网格的最小单位。
  • 长度灵活。用fr单位指定的尺寸, 它表示网格容器中自由空间的一部分。
CSS布局教程(从经典方法到最新技术)

文章图片
如果使用网格布局, 则可以使用以下替代标记:
< body class="layout-grid"> < header id="header"> < /header> < nav id="nav"> < /nav> < aside id="subnav"> < /aside> < main id="main"> < /main> < footer id="footer"> < /footer> < /body>

请注意, 使用这种布局, 我们不需要像使用flexbox那样为内容区域添加额外的包装, 因为这种类型的布局允许我们在同一网格容器中的两个方向上定义元素空间名称。
现在让我们深入研究CSS:
.layout-grid { display: grid; grid-template-columns: auto 0 auto 1em 1fr; grid-template-rows: 5em 1em 1fr 1em 3em; }

我们定义了显示:网格;在我们的容器上。分别将grid-template-columns和grid-template-rows属性指定为网格轨迹之间的空间列表。换句话说, 这些值不是网格线的位置;相反, 它们代表两条轨道之间的空间量。
注意, 度量单位可以指定为:
  • 一个长度
  • 网格容器大小的百分比
  • 占据列或行的内容的度量, 或者
  • 网格中自由空间的一小部分
因此, 使用grid-template-columns:auto 0 auto 1em 1fr; 我们将有:
  • 1条轨道, 定义2列自动宽度(#nav空间)
  • 1个装订线, 为0(#subnav的边距在元素级别, 无论是否存在, 这样我们就避免了双装订线)
  • 1条轨道, 定义2列自动宽度(#subnav空间)
  • 1个装订线或1em
  • 1条轨道, 每条#main使用1fr(将占用所有剩余空间)
在这里, 我们大量使用轨道的自动值, 这使我们可以拥有动态列, 其中行的位置和大小由行的最大内容来定义。 (因此, 我们需要指定#nav和#subnav元素的大小, 我们稍后将进行说明。)
同样, 对于行线, 我们有grid-template-rows:5em 1em 1fr 1em 3em;这将我们的#header和#footer设置为固定, 并且将所有元素之间设置为在使用1em装订线时使用剩余的可用空间。
现在, 让我们看看如何将实际元素放置到定义的网格中:
#header { grid-column: 1 / 6; grid-row: 1 / 2; }#footer { grid-column: 1 / 6; grid-row: 5 / 6; }#main { grid-column: 5 / 6; grid-row: 3 / 4; overflow-y: auto; }

这指定我们希望标题位于网格线1和6(全宽)之间, 以及行的网格线1和2之间。页脚相同, 但在最后两行之间(而不是前两行)。并且主要区域被设置为其应该占据的空间。
请注意, grid-column和grid-row属性是分别指定grid-column-start / grid-column-end和grid-row-start / grid-row-end的简写形式。
好的, 回到#nav和#subnav。由于我们之前使用自动值将#nav和#subnav放置在轨道中, 因此我们需要指定这些元素的宽度(与扩展模式相同, 我们只需更改其宽度, 而Grid Layout负责其余部分)。
#nav { width: 5em; grid-column: 1 / 2; grid-row: 3 / 4; & .expanded { width: 10em; } }#subnav { grid-column: 3 / 4; grid-row: 3 / 4; width: 13em; /* track has gutter of 0, so add margin here */ margin-left: 1em; }

因此, 现在我们可以切换#nav, 也可以隐藏/删除#subnav, 并且一切正常!网格布局还允许我们为行使用别名, 因此最终更改的网格不会将代码分解为映射到名称而不是网格线的代码。绝对希望更多的浏览器对此提供更广泛的支持。
总结 即使使用经典的CSS技术, 也可以完成许多网站开发人员无法意识到或无法利用的更多工作。话虽如此, 但其中的大部分内容可能非常繁琐, 并且可能涉及在整个样式表中反复对值进行硬编码。
CSS3已经开始提供更加复杂和灵活的布局技术, 这些技术很容易编程, 并且避免了以前CSS规范的繁琐工作。
掌握CSS2和CSS3的这些技术和范例对于利用CSS提供的所有内容以优化用户体验和代码质量至关重要。本文实际上只是代表了所有需要学习的东西的冰山一角, 并且可以使用CSS的强大功能和灵活性来完成所有这些工作。加油!
相关:*探索SMACSS:CSS的可扩展和模块化架构*

    推荐阅读