作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
劳雷亚诺·马丁·阿卡尼奥的头像

劳雷亚诺·马丁·阿卡尼奥

Laureano是一名web开发人员和设计师,在过去的15年中获得了各种技能, 与英特尔等客户合作.

Expertise

Previously At

intel
Share

掌握网页布局而不掌握CSS就像在旱地上学习游泳一样可行. 但不像游泳, once mastered, 是一项伴随你一生的技能——掌握CSS是一个永远不会结束的过程,因为CSS本身是不断发展的.

不同浏览器(甚至同一浏览器的不同版本)之间CSS实现和支持的差异加剧了这一挑战。, 以及采纳CSS建议的不同比率. 十多年了, Web设计人员和开发人员一直在努力应对零星和不一致的附加请求 支持CSS3特性 在每个新的浏览器版本.

但尽管如此, mastering CSS 是任何固体的绝对必需品吗 网页设计师或开发人员. 本文将引导您了解一些基本的CSS布局原则, 从经典的CSS2技术到最新的CSS3布局方法.

NOTE: 本文中的所有代码示例都使用 HTML5 elements and Sass syntax. 完整的工作代码可以从 http://github.com/laureanoarcanio/css-layout-examples.

Use Case

学习一项技术的最佳方法之一是有一个您试图支持的特定用例或一个您试图解决的特定问题. 为了达到这个目的,我们将把重点放在具有一组特定需求的用例上.

我们的用例包含一个带有一些动态行为的Web应用程序布局. 它将在页面上有固定的元素,例如标题, footer, 导航菜单和子导航, 以及一个可滚动的内容部分. 具体布局要求如下:

  • Basic Layout
    • 页眉、页脚、导航菜单和子导航都固定在滚动上
    • 导航和子导航元素占据所有垂直自由空间
    • 内容部分使用页面上所有剩余的可用空间,并有一个可滚动的区域
  • 动态行为
    • 导航菜单默认只显示图标, 但也可以展开显示文本(然后可以折叠再次只显示图标)
  • 布局的变化
    • 有些页面在导航菜单旁边有子导航,有些则没有

使用经典CSS2技术的CSS教程

CSS Tutorial

首先,下面是我们将使用经典CSS实现示例的HTML5标记:


  
  
  
  

固定定位

在CSS2中,我们可以实现页面上的固定元素(页眉、页脚等).),使用使用固定定位的定位布局模型.

另外,我们将使用 z-index 属性,以确保我们的固定元素保持在页面上的其他内容的“顶部”. The z-index 属性指定元素的堆栈顺序, 堆栈顺序较高的元素总是“在”堆栈顺序较低的元素的顶部. Note that the z-index 属性仅适用于 positioned elements. 在我们的例子中,我们任意使用a z-index 值为20(高于默认值),以确保我们的固定元素在视觉上保持在前列.

同样,我们会设置 width 属性设置为100%,指示浏览器在水平方向上使用该元素的所有可用空间.

#header, #footer {
  位置:固定;
  width: 100%;
  z-index: 20;
}

#header {
  top: 0;
  height: 5em;
}

#footer {
  bottom: 0;
  height: 3em;
}

这就是页眉和页脚. 但是呢? #nav and #subnav?

CSS Expansion

For the #nav and #subnav我们将使用一种稍微复杂一点的技术,叫做 CSS Expansion,可用于将元素定位为 fixed (i.e.(在页面上的固定位置)或作为 absolute (i.e.,相对于它最近的位置 positioned 父节点或包含块).

垂直扩展是通过设置两个 top and bottom 将元素的属性设置为固定值, 因此,元素将垂直扩展,以使用剩余的垂直空间. 基本上,你所做的就是将元素的顶部与页面顶部的特定距离绑在一起,并将元素的底部与页面底部的特定距离绑在一起, 所以元素扩展到填满这两点之间的垂直空间.

类似地,通过设置 left and right 将元素的属性设置为固定值, 因此,元素将水平扩展以使用剩余的水平空间.

对于我们的用例,我们需要使用垂直扩展.

#nav, #subnav {
  位置:固定;
  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 {
  边距:60 - 40 - 20;
}

And with that, 使用CSS2,我们已经满足了用例的基本布局需求, 但是我们仍然需要满足动态功能的额外需求.

使用经典CSS2技术的动态行为

需求说明我们的导航菜单默认情况下只显示图标, 但将能够扩展显示文本以及(然后可以折叠,再次只显示图标).

CSS2和CSS3教程

让我们从简单的加法开始 5em 到导航菜单展开时的宽度. 我们将通过创建一个“扩展的”CSS类来实现这一点,我们可以动态地从导航菜单元素中添加或删除它:

#nav {
  left: 0;
  width: 5em;
  &.扩展{/* Sass符号*/
    width: 10em;
  }
}

下面是JavaScript代码的一个示例(在这个示例中), 我们使用jQuery),它可以用来在展开和折叠模式之间动态切换导航菜单, 基于用户点击导航切换图标:

$('.layout-classic #导航”).(“点击”、“李.Nav-toggle ', function() {
  $('#nav’').toggleClass(扩大);
});

有了这个,我们的导航菜单现在可以动态地展开或折叠. Great.

还不错,但也不完全是. 虽然导航菜单现在可以展开和收缩,但它不能很好地与页面的其他部分配合. 展开的导航菜单现在与子导航重叠,这绝对不是期望的行为.

This reveals one of the key limitations of CSS2; namely, there is way 太多需要用固定位置值硬编码的内容. As a result, 对于页面上需要重新定位以适应扩展导航菜单的其他元素, 我们需要定义 additional “扩展”CSS类 yet more 固定位置值.

#subnav {
  left: 6em;
  width: 13em;
  &.expanded {
    left: 11em;    /* move it on over */
  }
}

#main {
  页边距:6em 0 4em 20;
  z-index: 10;
  &.expanded {
    margin-left: 25 em;    /* move it on over */
  }
}

然后,我们需要扩展我们的JavaScript代码,以便在用户单击导航开关时添加这些其他元素的动态调整:

$('.layout-classic #导航”).(“点击”、“李.Nav-toggle ', function() {
  $('#nav, #subnav, #main').toggleClass(扩大);
});

好了,这样就行了.

使用经典CSS2技术的布局变化

现在让我们来解决一些页面隐藏子导航菜单的需求. Specifically, 我们希望子导航菜单被隐藏时,用户单击“用户”图标在主导航区.

CSS图层教程

因此,首先,我们将创建一个新类“hidden”来应用 display: none:

.hidden {
  display: none;
}

同样,我们将使用JavaScript (jQuery)将“hidden”CSS类应用于 #subnav 元素,当用户单击users图标时:

$('#nav.fa-user').On ('click', function() {
  $('#subnav').toggleClass(隐含);
});

加上这个, #subnav 元素在用户点击“users”图标时被适当地隐藏, 但它所占据的空间仍未被使用,而不是其他元素扩展以利用空间 #subnav element.

为了得到想要的行为,我们隐藏了 #subnav 元素,我们将使用一个不太为人所知但非常有用的CSS选择器,即 相邻兄弟选择器.

相邻兄弟CSS选择器

The 相邻兄弟选择器 允许您指定两个元素, 只选择紧跟在指定的第一个元素之后的第二个元素的实例.

例如,下面的代码将只选择具有ID的元素 main that immediately 跟在带有ID的元素后面 subnav:

#subnav + #main {
  margin-left: 20 em;
}

的左边距 #main to 20em if and only if 它立即跟随显示的#subnav.

However, if #nav 是膨胀的(这导致 expanded 要添加到的类 #main 同样,根据前面的代码,我们移动的左边距 #main to 25em.

#subnav + #main.expanded {
  margin-left: 25 em;
}

And, if #subnav 是隐藏的,我们移动左边的边距 #main 一直到6em就在旁边 #nav:

#subnav.隐藏+ #main {
  margin-left: 6 em;
}

(注意:使用相邻兄弟选择器的一个缺点是它迫使我们总是有 #subnav 显示在DOM中,而不管它是否被显示.)

Finally, if #subnav is hidden and #nav 是展开的,我们设置左边距 #main at 11em:

#subnav.hidden + #main.expanded {
  margin-left: 11 em;
}

这使我们能够在没有任何繁重的JavaScript代码的情况下将东西连接在一起, 但是我们也可以看到,如果向页面添加更多元素,代码会变得多么复杂. 我们再次看到,使用CSS2, lots 为了使事情正常工作,需要硬编码位置值.

利用CSS3

CSS3提供了显著增强的功能和布局技术,使其更容易使用,更少依赖于硬编码值. CSS3天生就支持更多的动态行为, in that sense, “可编程”. 让我们来看看与我们的用例相关的一些新功能.

CSS3 calc() Function

The new CSS3 calc() function 可以用来动态计算CSS属性值(不过请注意, support varies 跨浏览器). 的表达式 calc() 函数可以是结合基本算术运算符(+, -, *, /),使用标准操作符优先规则.

Use of the calc() 函数可以帮助避免CSS2所需的许多值的硬编码. 在我们的例子中,这使我们能够更动态地实现CSS扩展. For example:

#nav, #subnav {
  位置:固定;
  height: calc(100% - 10em); /* replaces */
  z-index: 20;
}

With the above height 规范使用 calc() 函数,我们实现了与CSS2中相同的结果 top:6em and bottom:4em,但以一种更加灵活和自适应的方式,而不需要硬编码 top and bottom 位置值.

CSS3 Flexbox布局

Flexbox 是CSS3 (不同浏览器的支持不同). flexbox布局使得在页面上以一种可预测的方式排列元素在不同屏幕尺寸上更简单, resolutions, and devices. 因此,它在下列情况下特别有用 响应式网页设计.

主要特点包括:

  • 定位子元素要容易得多,复杂的布局可以用更简洁的代码更简单地实现.
  • 子元素可以在任何方向上布局,并且可以具有灵活的尺寸以适应显示空间.
  • 子元素自动扩展契约以适应可用的空闲空间.

Flexbox引入了自己独特的术语和概念. 其中一些包括:

  • Flex container. 具有其 display 属性设置为 flex or inline-flex 哪个作为伸缩项的容器元素.
  • Flex item. 伸缩容器中的任何元素. (注意:直接包含在flex容器中的文本被封装在一个匿名的flex项中.)
  • Axes. 每个flexbox布局都有 flex-direction 它指定了 main axis 伸缩项沿其布局. The cross axis 那么轴是否垂直于主轴.
  • Lines. Flex项目可以根据控件的要求在单行或多行上进行布局 flex-wrap property.
  • Dimensions. 高度和宽度的flexx等价物是 main size and cross size,分别指定伸缩容器的主轴和交叉轴的大小.

OK, 简单介绍一下, 如果我们使用flexx布局,这里是我们可以使用的替代标记:


  
  

对于我们的示例用例, 我们的主布局(头部), content, 页脚)是垂直的, 所以我们将flexx设置为使用列布局:

.layout-flexbox {
  display: flex;
  flex-direction:列;  
}

虽然我们的主要布局是垂直的, 内容区域中的元素(nav, subnav, 主)是水平布局的. 每个伸缩容器只能定义一个方向(i.e.,其布局必须是水平或垂直的). Therefore, 当布局需要比这更多(一个常见的情况是应用程序的布局), 我们可以将多个容器嵌套在一起, 每个都有不同的方向布局.

这就是为什么我们添加了一个额外的容器(我称之为 content-area) wrapping #nav, #subnav, and #main. This way, 整体布局可以是垂直的, 而内容区的内容可以水平布局.

现在,为了定位我们的伸缩项,我们将使用属性 flex 这是一个简写 flex: ;. 这三个伸缩属性决定了我们的伸缩项如何在流动方向上分配它们之间剩余的自由空间, as follows:

  • flex-grow: 指定一个项相对于同一容器内的其他灵活项可以增长多少
  • flex-shrink: 指定项相对于同一容器内其他灵活项的收缩方式
  • flex-basis: 指定项的初始大小(i.e.(在它缩小或变大之前)

CSS flex容器:Cross vs main

Setting flex-grow and flex-shrink 两者都为零表示该项的大小为 fixed 它不会因可用空间的增加或减少而增加或缩小. 这就是我们对页眉和页脚所做的,因为它们有固定的大小:

#header {
  flex: 0 0 5em;
}

#footer {
  flex: 0 0 3em;
}

要让一个项目占用所有可用的空闲空间,请设置其 flex-grow and flex-shrink 值为1,并设置其 flex-basis value to auto. 这就是我们对内容区域所做的,因为我们希望它占用所有可用的空闲空间.

就像我们之前说的,我们想要里面的项 content-area 为了按行方向排列,我们要加上 display: flex; and flex-direction:行;. 这使得content-area成为一个新的flex容器 #nav, #subnav and `#main.

这就是CSS的最终目的 content-area:

.content-area {
    display: flex;
    flex-direction:行;
    Flex: 1 1 auto; /* take up all available space */
    margin: 1em 0;
    min-height: 0; /* fixes FF issue with minimum height */
}

在内容区域,两者都有 #nav and #subnav 有固定的大小,所以我们只需要设置 flex 相应的属性:

#nav {
  flex: 0 0 5em;
  margin-right: 1 em;
  overflow-y:汽车; 
}

#subnav {
  Flex: 0 0 13em;
  overflow-y:汽车; 
  margin-right: 1 em;
}

(注意,我添加了 overflow-y:隐藏 这些CSS规范,以克服内容超过和溢出的容器高度. Chrome实际上不需要这个,但是FireFox需要.)

#main 将占用剩余的空闲空间:

#main {
  Flex: 1 1 auto;
  overflow-y:汽车; 
}

这些看起来都很好,现在让我们把动态行为添加进去,看看效果如何.

JavaScript与我们之前的相同(除了这里), 我们指定的CSS元素容器类是 layout-flexbox 而之前是 layout-classic):

$('.layout-flexbox #导航”).(“点击”、“李.Nav-toggle ', function() {
  $('#nav').toggleClass(扩大);
});

We add the expanded 类转换为CSS如下:

#nav {
  flex: 0 0 5em; /* collapsed size */
  margin-right: 1 em;
  overflow-y:汽车; 
  &.expanded {
    flex: 0 0 10em; /* expanded size */
  }
}

And voila!

请注意,这次我们不需要让其他项目知道宽度的变化, 因为flexbox布局为我们处理了所有这些.

唯一剩下的就是把潜艇导航藏起来了. And guess what? 这也“正好工作”,不需要任何额外的更改,使用与以前相同的JavaScript代码. Flexbox知道自由空间,它自动使我们的布局工作,没有额外的代码. Pretty cool.

Flexbox还提供了一些有趣的方式来居中垂直和水平元素. 我们在这里意识到,对于一种表示语言来说,包含自由空间的概念是多么重要,以及使用这些技术我们的代码可以变得多么可伸缩. 另一方面,这里的概念和符号可能比经典CSS需要更多的时间来掌握.

CSS3网格布局

如果Flexbox布局是在CSS3的前沿, 那么网格布局可以说是在其流血的边缘. W3C规范仍处于草案状态 浏览器支持相当有限. (它在Chrome中通过“实验性Web平台功能”标志启用 chrome://flags).

也就是说,我个人并不认为这个草案具有革命性. Rather, as the HTML5设计原则 state: 当一种做法在作者中已经很普遍的时候, 考虑采用它,而不是禁止它或发明新的东西.”

Accordingly, 基于标记的网格已经使用了很长时间, 所以现在CSS网格布局实际上只是遵循相同的范式, 在没有标记需求的情况下,提供了它的所有优点,并在表示层提供了更多的优点.

一般的想法是有一个预定义的, fixed, 或者灵活的网格布局,我们可以定位我们的元素. 比如flexbox, 它也适用于自由空间原则,允许我们在同一元素中定义垂直和水平“方向”, 哪个在代码大小和灵活性上有优势.

Grid layout introduces 2 types of grids; namely, explicit and implicit. 为了简单起见,我们将重点放在显式网格上.

与flexbox一样,网格布局也引入了自己独特的术语和概念. 其中一些包括:

  • Grid container. 具有其 display 属性设置为“grid”或“inline-grid”,其中包含的元素通过定位和对齐预定义的网格来布局(显式模式). 网格是一组相交的水平和垂直网格线,将网格容器的空间划分为网格单元. There are two sets of grid lines; one for defining the columns and one orthogonal to it for defining the rows.
  • Grid track. 网格:两条相邻网格线之间的空间. 每个网格轨道被分配一个大小函数, 哪个控件控制列或行可以增长多宽或多高, 也就是它的边界网格线之间的距离.
  • Grid cell. 两个相邻行和两个相邻列网格线之间的空间. 它是定位网格项时可以参考的网格的最小单位.
  • Flexible length. 指定的维度 fr 单元,它表示网格容器中可用空间的一小部分.

CSS网格布局示意图

如果我们使用网格布局,这里是我们可以使用的替代标记:


  
  
  
  

注意,在这个布局中,我们这样做 not 内容区域需要一个额外的包装器,就像我们为flexbox所做的那样, 由于这种布局类型允许我们在同一个网格容器的两个方向上定义元素空间.

现在让我们深入了解CSS:

.layout-grid {
  display: grid;
  网格模板列:auto 0 auto 1em 1fr;
  网格模板行:5em 1em 1em 3em;
}

We’ve defined display: grid; 在我们的集装箱上. The grid-template-columns and grid-template-rows 每个属性都被指定为网格轨道之间的空格列表. In other words, those values are not the position of the grid lines; rather, they represent the 空间量 两轨之间.

注意,测量单位可以指定为:

  • a length
  • 网格容器大小的百分比
  • 对所占列或行内容的测量
  • 网格中自由空间的一小部分

So with 网格模板列:auto 0 auto 1em 1fr; we will have:

  • 1个轨道定义2列 auto width (#nav space)
  • 0的边距 #subnav 是在元素级别,因为它可以存在或不存在,这样我们就避免了双沟)
  • 1个轨道定义2列 auto width (#subnav space)
  • 1 gutter of 1em
  • 1 track using 1fr for #main (将占用所有剩余空间)

这里我们大量使用 auto 轨道的值, 这允许我们拥有动态列,其中行位置和大小由其最大内容定义. (因此,我们需要指定的尺寸 #nav and #subnav 元素,我们很快就会讲到.)

类似地,对于我们已知的行线 网格模板行:5em 1em 1em 3em; that sets our #header and #footer 要固定和所有元素之间,使用时要使用剩余的自由空间 1em gutters.

现在让我们看看如何将实际的元素放置到我们定义的网格中:

#header {
  格柱:1 / 6;
  格列:1 / 2;
}

#footer {
  格柱:1 / 6;
  格列:5 / 6;
}

#main {
  格柱:5 / 6;
  格列:3 / 4;
  overflow-y:汽车; 
}

这指定我们想要我们的标题在网格线1和6之间(全宽度), 在网格线1和2之间. 页脚也一样,但在最后两行之间(而不是前两行). 主要区域的设置与它应该占据的空间相适应.

Note that the grid-column and grid-row 属性是指定的简写 grid-column-start / grid-column-end and grid-row-start / grid-row-end, respectively.

好,回到 #nav and #subnav. 因为我们之前 #nav and #subnav 进入轨道与自动值, 我们需要指定这些元素的宽度(对于扩展模式也是如此), 我们只需要改变它的宽度,网格布局会处理好剩下的部分).

#nav {
  width: 5em;
  格柱:1 / 2;
  格列:3 / 4;
  &.expanded {
    width: 10em;
  }
}

#subnav {
  格柱:3 / 4;
  格列:3 / 4;
  width: 13em;
  轨道的边距为0,所以在这里添加边距*/
  margin-left: 1 em;
}

现在我们可以切换 #nav 还有隐藏/移除 #subnav 一切都很完美! 网格布局也允许我们为我们的线条使用别名, 所以最终改变网格不会打破代码,因为它被映射到一个名称,而不是网格线. 我非常期待更多的浏览器能更广泛地支持它.

Conclusion

即使使用经典的CSS技术, 比起许多web开发人员意识到或利用到的,还有更多可以完成的事情. That said, 其中大部分工作可能相当乏味,并且可能涉及在整个样式表中重复地对值进行硬编码.

CSS3已经开始提供更加复杂和灵活的布局技术 更容易编程 并且避免了以前的CSS规范的许多乏味之处.

掌握这些技术和范例——无论是针对CSS2还是CSS3——对于利用CSS所提供的所有功能来优化用户体验和代码质量都是必不可少的. 本文实际上只是冰山一角,展示了所有需要学习的东西,以及CSS的强大功能和灵活性所能实现的一切. Have at it!

聘请Toptal这方面的专家.
Hire Now
劳雷亚诺·马丁·阿卡尼奥的头像
劳雷亚诺·马丁·阿卡尼奥

Located in Córdoba,阿根廷科尔多瓦

Member since 2014年2月5日

作者简介

Laureano是一名web开发人员和设计师,在过去的15年中获得了各种技能, 与英特尔等客户合作.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

intel

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal开发者

加入总冠军® community.