CSS布局学习指南[译]

原文:Getting Started With CSS Layout_Rachel Andrew

引言

无论你是一个想要学习CSS布局的新手,还是一个比较有经验但想要进一步巩固与了解最新的CSS布局知识的前端开发者,这篇指南都能帮到你全面了解如今CSS布局发展的现状。

在过去的许多年中,正如翻天覆地的前端开发一般,CSS布局也产生了巨大的变化。现在我们有需要可选的CSS布局方式来开发我们的网站,这也就要求我们对这些方式作出选择。在这片文章里,我会介绍各种CSS布局的基本使用方式以及使用的目的。

如果你还是CSS方面的新手并且想要了解什么是最好的布局方法,这篇文章正式你所需要的;当然,如果你是一位比较有经验的开发者,想要了解一些关于CSS布局的最新知识,这篇文章也不容错过。当然,我不会将各类技术的细枝末节都放到这篇文章里(否则可以写一本书了),而是对各类技术做一个基本的概述,同时会给大家提供相关链接来进一步学习。

1. 正常文档流(Normal Flow)

如果你打开一个没有用任何CSS来改变页面布局的网页,那么网页元素就会排列在一个正常流(normal flow)之中。在正常流中,元素盒子(boxes)会给予文档的书写模式(writing mode)一个接一个地排列。这就意味着,如果你的写作模式是水平方向的(句子是从左到右或从右到左书写),正常流会垂直地一个接一个地排列页面的块级元素。

当然,如果你是在一个垂直方向的书写模式下,句子是垂直方向书写的,所以块级元素会水平方法排列。

Block and Inline Directions change with Writing Mode

正常流是一种最基础的布局:当你为文档应用了CSS、创建了某些CSS布局,你其实是让这些块做了一个正常文档流之外的“事”。

1.1. 通过页面结构来发挥正常文档流的优势

通过确保你书写页面具有良好的页面结构(well-structured manner),你可以最大程度利用正常流所带来的优势。试想一下,如果没有浏览器没有正常流,那么你创建的所有元素都会堆积在浏览器的右上角。这就意味着你必须指定所有的元素的布局方式。

有了正常流,即使CSS加载失败了,用户仍然能阅读你的页面内容;同时,一些不使用CSS的工具(例如一些阅读器)会按照元素在文档中的位置来读取页面内容。从 可用性(accessibility) 角度来看,这无疑是非常有帮助的,同时也让开发者轻松了一些。如果你的内容顺序和用户预期的阅读顺序一致,你就不需要为了将元素调整到正确的位置而进行大量的布局调整。当你继续读下去会发现,使用新的布局方式是如何让页面布局事半功倍的。

因此,在思考如何布局之前,你需要认真思考你的文档结构,以及你希望用户以何种顺序来阅读文档中自上而下的各个内容。

1.2. 脱离正常文档流

一旦你有了一个结构良好的页面,你就需要去决定如何利用它并将它变为我们需要的布局结构。这会涉及到 脱离正常文档流(moving away from normal flow),即本文后续的部分内容。我们有需要布局“利器”可以使用,其中第一个就是float,它是一个非常好的例子来描述什么是脱离正常文档流。

2. 浮动(Float)

浮动被用来将一个盒子置于左侧或右侧,同时让内容环绕其展示。

要让一个元素进行浮动,需要为该元素设置一个值为left或right的float属性。默认值为none。

.item {
    float: left
}

值得强调的是,当你使某个元素浮动并让文字环绕它时,内容的line box被截断了。如果你时使一个元素浮动,且为紧接着的包含文本的元素(box)设置一个背景色,你会发现背景色会出现在浮动元素下方。

The background color on the content runs under the float

如果你想要在浮动元素和环绕的文本之间创建边距,你需要给浮动元素设置外边距。在文本元素上设置外边距只会让其相对于容器缩紧。例如在下面这个例子中,你就需要为左侧浮动的图片设置右边距和下边距。

<div class="container">
  <div class="item"></div>
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce. </p>
  <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
}

.item {
  width: 100px;
  height: 100px;
  float: left;
  margin: 0 20px 20px 0;
  background-color: rgba(111,41,97,.3);
}

2.2. 清除浮动

一旦你对一个元素应用了浮动,所有接下来的元素都会环绕它直到内容处于它下方且开始应用正常文档流。如果你想要避免这种情况,可以手动去清除浮动。

当你不想要某个元素收到其之前的浮动元素影响时,为其添加clear属性即可。使用left值可以清除左浮动效果,right值为右浮动,both则会清除左右浮动。

.clear {
    clear: both;
}

但是,当你发现在容器内有了一个浮动元素,同时容器文本内容过短时就会出现问题。文本盒子会被绘制在浮动元素下,然后接下来的部分会以正常流绘制在其后。

The box around the text does not clear the float

为了避免这种情况,我们需要为容器中某个元素应用clear属性。我们可以在容器最后添加一个空元素并设置clear属性。但是在某些情况下这个方式可能无法做到(例如一些CMS系统生成的页面)。因此,最常见的清除浮动的hack方案是:在容器内添加一个CSS伪元素,并将其clear属性设置为both。

<div class="container">
  <div class="item"></div>
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
}

.item {
  width: 100px;
  height: 100px;
  float: left;
  margin: 0 20px 20px 0;
  background-color: rgba(111,41,97,.3);
}

.container::after {
  content: "";
  display: table;
  clear: both;
}

2.3. BFC - The Block Formatting Context

清除浮动的另一个方法是在容器内创建BFC。一个BFC元素完全包裹住了它内部的所有元素,包括内部的浮动元素,保证浮动元素不会超出其底部。创建BFC的方式有很多种,其中最常用的一种清楚浮动的方式是为元素设置除visible(默认)之外的overflow属性值。

.container {
    overflow: auto;
}

想上面这样使用overflow一般情况下是有效的。然而,在某些情况下,这可能会带来一些阴影的截断或是非预期的滚动条。同时它也使你的CSS变得不那么直观:设置overflow是因为你想要展示滚动条还是仅仅为了获取清除浮动的能力呢?

为了使清除浮动的意图更加直观,并且避免BFC的负面影响,你可以使用flow-root作为display属性的值。display: flow-root做的唯一的一件事就是去创建一个BFC,因此可以避免其他创建BFC方法带来的问题。

.container {
    display: flow-root;
}

2.4. 浮动的一些遗留用法

在新的布局方式出现以前,float经常会被用来创建多栏布局。我们会给一系列元素设置宽度并且将它们一个接一个进行浮动。通过为浮动元素设置一些精细的百分比大小可以创建类似网格的效果。

我不建议当下仍然过度地使用这种方法。但是,在现有的网站中,这种方式仍然会存在许多年。因此,当你碰到一个页面里面到处是float的应用,可以确定它就是用的这类技术。

2.5. 关于浮动与清除浮动的其他阅读资料

3. 定位(Positioning)

想要把一个元素从正常流中移除,或者改变其在正常文档流中的位置,可以使用CSS中的position属性。当处于正常文档流时,元素的position属性为static。在块级的维度上元素会一个接一个排列下去,当你滚动页面时元素也会随着滚动。

当你改变元素的position属性时,通常情况下你也会设置一些偏移量来是元素相对于参照点进行一定的移动。不同的position值会产生不同的参照点。

3.1. 相对定位(relative postioning)

如果一个元素的具有属性position: relative,那么它偏移的参照位是其原先在正常文档流中的位置。你可以使用top、left、bottom和right属性来相对其正常流位置进行移动。

.item {
    position: relative;
    bottom: 50px;
}

注意,页面上的其他元素并不会因该元素的位置变化而收到影响。该元素在正常流中的位置会被保留,因此你需要自己去处理一些元素内容覆盖的情况。

<div class="container">
  
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce. </p>
  
  <div class="item"></div>
  <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
}

.item {
  width: 100px;
  height: 100px;
  background-color: rgba(111,41,97,.3);
  position: relative;
  bottom: 50px;
}

3.2. 绝对定位(absolute postioning)

给一个元素设置position: absolute属性可以将其完全从正常流中移除。其原本占据的空间也会被移除。该元素会定位会相对于视口容器,除非其某个祖先元素也是定位元素(position值不为static)。

因此,当你为某个元素设置position: absolute时,首先发生的变化是该元素会定位在视口的左上角。你可以通过设置topleftbottomright偏移量属性来将元素移动到你想要的位置。

.item {
    position: absolute;
    top: 20px;
    right: 20px;
}

通常情况下你并不希望元素相对于视口进行定位,而是相对于其容器元素。在这种情况下,你需要为容器元素设置一个除了默认static之外的值。

由于给一个元素设置position: relative并不会将其从正常流中移除,所以通常这是一个不错的选择。给你想要相对的容器元素设置position
: relative
,就可以让绝对定位的元素相对其进行偏移。

<div class="container">
  
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce. </p>
  
  <div class="item"></div>
  <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  position: relative;
}

.item {
  width: 100px;
  height: 100px;
  background-color: rgba(111,41,97,.3);
  position: absolute;
  top: 20px;
  left: 20px;
}

3.3. 固定定位(fixed positioning)

大多数情况下,position: fixed的元素会相对于视口定位,并且会从正常文档流中被移除,不会保留它所占据的空间。当页面滚动时,固定的元素会留在相对于视口的位置,而其他正常流中的内容则和通常一样滚动。

.item {
    position: fixed;
    top: 20px;
    left: 100px;
}

当你想要一个固定导航栏一直停留在屏幕上时这会非常有效。和其他的position值一样,这也可能会造成一些元素被遮挡,需要小心保证页面内容的可读而不会被固定元素遮挡。

<div class="container">
  
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce. </p>
  
  <div class="item"></div>
  <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
  
   <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
  
   <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  position: relative;
}

.item {
  width: 100px;
  height: 100px;
  background-color: rgba(111,41,97,.3);
  position: fixed;
  top: 20px;
  left: 20px;
}

为了使一个固定定位的元素不相对于视口进行定位,你需要为容器元素设置transformperspectivefilter三个属性之一(不为默认值none)。这样fixed元素就会相对于该块级元素偏移,而非视口。

3.4. STICKY 定位

设置position: sticky会让元素在页面滚动时如同在正常流中,但当其滚动到相对于视口的某个特定位置时就会固定在屏幕上,如同fixed一般。这个属性值是一个较新的CSS属性,在浏览器兼容性上会差一些,但在不兼容的浏览器中会被忽略并会退到正常的滚动情况。

.item {
    position: sticky;
    top: 0;
}

下面的代码展示了如何创建一个非常流行导航栏效果:导航栏会随着页面滚动,而当导航栏滚动到页面顶部时则会固定在顶部位置。

<div class="container">
  
  <p>Pea horseradish azuki bean lettuce avocado asparagus okra. Kohlrabi radish okra azuki bean corn fava bean mustard tigernut jícama green bean celtuce. </p>
  
  <div class="item"></div>
  <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
  
   <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
  
   <p>Grape silver beet  collard greens avocado quandong fennel gumbo black-eyed pea watercress potato tigernut corn groundnut. Chickweed okra pea winter purslane coriander yarrow sweet pepper radish garlic brussels sprout groundnut summer purslane earthnut pea tomato spring onion azuki bean gourd. Gumbo kakadu plum komatsuna black-eyed pea green bean zucchini gourd winter purslane silver beet rock melon radish asparagus spinach.</p>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  position: relative;
}

.item {
  width: 100px;
  height: 30px;
  background-color: rgba(111,41,97,.3);
  position: sticky;
  top: 0;
  width: 100%;
}

3.5. 关于定位(positioning)的其他阅读资料

4. Flex布局

弹性盒子布局(Flexbox)是一种为一维布局而设计的布局方法。一维的意思是你希望内容是按行或者列来布局。你可以使用display: flex来将元素变为flex布局。

.container {
    display: flex;
}

该容器的直接子元素会变为flex item,并按行排列。

4.1. 弹性盒子的轴(axes)

在上面的例子中,我们会称flex item在行内是从起始位置开始排列,而不是说它们是左对齐。这些元素会按行排列是因为默认的flex-direction值为rowrow代表了文本的行文方向。由于我们工作的环境是英文(中文也是如此),一种自左向右的语言,行的开始位置就是在左边,因此我们的flex item也是从左边开始的。flex-direction的值因此被定义为弹性盒子的主轴(main axis)

交叉轴(cross axis)则是和主轴垂直的一条轴。如果你的flex-directionrow并且items是按照行内方向排列的,那么交叉轴就是块级元素的排列方向。如果flex-directioncolumn那么items就会以块级元素排列的方向排布,然后交叉轴就会变为row

如果你习惯于从主轴与交叉轴的角度来使用弹性盒子,那么一切会变得更简单。

4.2. 方向和次序

弹性盒子模型让我们可以通过为flex-direction属性设置row-reversecolumn-reverse值来改变主轴上元素(flex items)的方向。

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
  flex-direction: row-reverse;
}

.item {
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

当然,你也可以通过order属性来改变某一个flex item的顺序。但是要特别注意,这可能会给那些通过键盘(而非鼠标或触屏)访问你的网站的用户带来一些麻烦,因为tab的顺序是页面内元素在源码中的顺序而非显示顺序。你可以阅读之后的“显示和文档顺序”部分来了解更多相关内容。

4.3. 一些Flex的属性

这些flex的属性是用来控制flex item在主轴上空间大小的。这三个属性是:

  • flex-grow
  • flex-shrink
  • flex-basis

通常可以使用它们的简写形式:flex。第一个值代表flex-grow,第二个是flex-shrink,而第三个则是flex-basis

.item {
    flex: 1 1 200px;
}

flex-basis会为flex item设置为经拉伸和压缩时的初始大小。在上面的例子中,大小时200px,因此我们会给每个item 200px的空间大小。但是大多数情况下容器元素大小不会正好被分为许多200px大小的item,而是可能有一些不足或剩余空间。flex-growflow-shrink属性允许我们在容器大小不足或又空余时控制各个flex item的大小。

如果flex-grow的值时任意的正数,那么flex item就被允许拉伸来占据更多的空间。因此,在上面的例子中,当各个item被设为200px后,所有多余的空间会被每个item平分并填满。

如果flex-shrink的值为任意的正数,那么当flex item被设置了flex-basis后元素大小溢出容器时会进行收缩。在上面这个CSS的例子中,如果容器空间不足,每个flex item会等比例缩放以适应容器的大小。

flex-growflex-shrink的值可以是任意的正数。一个具有较大flex-grow值的item会在容器有剩余空间时拉伸更大的比例;而一个具有更大flex-shrink值的元素,则会在容器空间不足时被压缩的更多。

<div class="container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: flex;
}

.item {
  flex: 1 1 0;
  width: 100px;
  height: 100px;
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

.container :first-child {
  flex: 2 1 0; 
}

理解这些属性是理解如何使用弹性布局的关键,下面列出的一些资源会帮助我们进一步学习其中的细节。当你需要在容器的一个维度上拉伸或者压缩一些元素时,你可以考虑使用弹性盒子模型。如果你发现你正尝试在行和列两个维度上排列你的内容,你需要的是网格模型(grid),这时弹性盒子模型很可能不是最合适的工具了。

4.4. 关于弹性盒子布局的其他阅读资料

5. 网格布局(grid layout)

CSS网格布局(grid layout)是一种用来进行二维布局的技术。二维(two-dimesional)意味着你希望按照行和列来排布你的内容。和弹性盒子类似,网格布局也需要设置一个display值。你可以为容器元素设置display: grid,并且使用grid-template-columnsgrid-template-rows属性来控制网格中的行与列。

.container {
    display: grid;
    grid-template-columns: 200px 200px 200px;
    grid-template-rows: 200px 200px;
}

上面这段CSS会创建一个行列元素大小固定的网格。不过这也许并不是你希望的。默认值为auto,你可以认为这代表了“让格子尽可能的大”。如果你每没有指定行(row track)的大小,所有添加进来的行内容大小都会被置为auto。一种常用的模式是为网格制定列宽度,但是允许网格按需添加行。

你可以使用任意的长度单位或时百分比来设置行与列,同时你可以使用为网格系统所创造的新的单位——frfr是一种弹性单位,它可以指定网格容器内的空间被如何划分。

网格会替你计算与分配空间,你不需要去计算元素的百分比去适应容器大小。在下面这个例子中,我们使用fr来创建网格的列,这使得网格的列可以自适应。同时我们还使用了grid-gap来保证元素间的间距(关于网格内元素与的间距会在“对齐”这一部分详细介绍)。

<div class="container">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5<br>has more content.</div>
</div>
body {
  padding: 20px;
  font: 1em Helvetica Neue, Helvetica, Arial, sans-serif;
}

* {box-sizing: border-box;}

p {
  margin: 0 0 1em 0;
}

.container {
  width: 500px;
  border: 5px solid rgb(111,41,97);
  border-radius: .5em;
  padding: 10px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 20px;
}

.container > div {
  padding: 10px;
  background-color: rgba(111,41,97,.3);
  border: 2px solid rgba(111,41,97,.5);
}

5.1. 关于网格的一些术语