层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML或XML等文件样式的计算机语言
1. 正常文档流(Normal Flow)
没有用任何CSS来改变页面布局的网页,那么网页元素就会排列在一个正常流(normal flow)之中。在正常流中,元素盒子(boxes)会基于文档的写作模式(writing mode)一个接一个地排列。意味着,如果你的写作模式是水平方向的(句子是从左到右或从右到左书写),正常流会垂直地一个接一个排列页面的块级元素
1
CSS 能够对网页中元素位置的排版进行像素级精确控制,支持几乎所有的字体字号样式,拥有对网页对象和模型样式编辑的能力
从HTML被发明开始,样式就以各种形式存在
正常流是一种最基础的布局:当你为文档应用了CSS、创建了某些CSS布局,你其实是让这些块做了一个正常文档流之外的“事”
1.1. 通过页面结构来发挥正常文档流的优势
通过确保你书写的页面具有良好的页面结构(well-structured manner),你可以最大程度利用正常流所带来的优势。试想一下,如果浏览器中没有正常流,那么你创建的元素都会堆积在浏览器的右上角。这就意味着你必须指定所有的元素的布局方式。
有了正常流,即使CSS加载失败了,用户仍然能阅读你的页面内容;同时,一些不使用CSS的工具(例如一些阅读器)会按照元素在文档中的位置来读取页面内容。如果你的内容顺序和用户预期的阅读顺序一致,你就不需要为了将元素调整到正确的位置而进行大量的布局调整
1.2. 脱离正常文档流
一旦你有了一个结构良好的页面,你就需要去决定如何利用它并将它变为我们需要的布局结构。这会涉及到 脱离正常文档流(moving away from normal flow),后续的部分内容有许多布局可以学习使用。
2. 浮动(Float)
float
属性定义元素在哪个方向浮动。以往这个属性总应用于图像,使文本围绕在图像周围,不过在 CSS 中,任何元素都可以浮动。浮动元素会生成一个块级框,而不论它本身是何种元素。如果浮动非替换元素,则要指定一个明确的宽度;否则,它们会尽可能地窄。
浮动被用来将盒子(box)置于左侧或右侧,同时让内容环绕其展示。要让一个元素进行浮动,需要为该元素设置一个值为left
或right
的float属性。默认值为none
。
当你使某个元素浮动并让文字环绕它时,内容的line box被截断了。如果你让一个元素浮动,同时为紧跟着的包含文本的元素设置一个背景色,你会发现背景色会出现在浮动元素下方
.item{
float: left;
width: 100px;
height: 100px;
background-color: pink;
}
如果你想要在浮动元素和环绕的文本之间创建边距,你需要给浮动元素设置外边距。在文本元素上设置外边距只会让其相对于容器缩进。例如在下面这个例子中,你就需要为左侧浮动的图片设置右边距和下边距
.item{
margin: 0 30px 30px 0;
}
2.1. 清除浮动
一旦你对一个元素应用了浮动,所有接下来的元素都会环绕它直到内容处于它下方且开始应用正常文档流。如果你想要避免这种情况,可以手动去清除浮动。
当你不想要某个元素受到其之前的浮动元素影响时,为其添加clear: both
属性即可。使用left值可以清除左浮动效果,right值为右浮动,both则会清除左右浮动。
但是,当你发现在容器内有了一个浮动元素,同时容器文本内容过短时就会出现问题。文本盒子会被绘制在浮动元素下,然后接下来的部分会以正常流绘制在其后。
为了避免这种情况,我们需要为容器中某个元素应用clear属性。我们可以在容器最后添加一个空元素并设置clear属性。但是在某些情况下可能无法使用这种方式。因此,比较常见的清除浮动的方案是:在容器内添加一个CSS伪元素,并将其clear属性设置为both。
.container p::after{
content: "";
display: block;
clear: both;
}
2.2. 块级格式化上下文(Block Formatting Context)
清除浮动的另一个方法是在容器内创建BFC。一个BFC元素完全包裹住了它内部的所有元素,包括内部的浮动元素,保证浮动元素不会超出其底部。创建BFC的方式有很多种,其中最常用的一种清除浮动的方式是为元素设置除visible(默认)之外的overflow: auto
属性值
像上面这样使用overflow一般情况下是有效的。然而,在某些情况下,这可能会带来一些阴影的截断或是非预期的滚动条。同时它也使你的CSS变得不那么直观:为了使清除浮动的意图更加直观,并且避免BFC的负面影响,你可以使用flow-root作为display属性的值。display: flow-root
做的唯一的一件事就是去创建一个BFC,因此可以避免其他创建BFC方法带来的问题。
2.3. 浮动的一些遗留用法
在新的布局方式出现以前,float经常会被用来创建多栏布局。我们会给一系列元素设置宽度并且将它们一个接一个进行浮动。通过为浮动元素设置一些精细的百分比大小可以创建类似网格的效果。
我不建议在当下仍然过度地使用这种方法。但是,在现有的网站中,这种方式仍然会存在许多年。因此,当你碰到一个页面里面到处是float的应用,可以确定它就是用的这种技术。
3. 定位(Positioning)
想要把一个元素从正常流中移除,或者改变其在正常文档流中的位置,可以使用CSS中的position属性。当处于正常文档流时,元素的position属性为static。在块级维度上元素会一个接一个排列下去,当你滚动页面时元素也会随着滚动。
当你改变元素的position属性时,通常情况下你也会设置一些偏移量来使元素相对于参照点进行一定的移动。不同的position值会产生不同的参照点。
3.1. 相对定位(relative postioning)
如果一个元素具有属性position: relative
,那么它偏移的参照位是其原先在正常文档流中的位置。你可以使用top、left、bottom和right属性来相对其正常流位置进行移动。
注意,页面上的其他元素并不会因该元素的位置变化而受到影响。该元素在正常流中的位置会被保留,因此你需要自己去处理一些元素内容覆盖的情况。
P1
1
P2
3.2. 绝对定位(absolute postioning)
给一个元素设置position: absolute
属性可以将其完全从正常流中移除。其原本占据的空间也会被移除。该元素会定位会相对于视口容器,除非其某个祖先元素也是定位元素(position值不为static)。
因此,当你为某个元素设置position: absolute时,首先发生的变化是该元素会定位在视口的左上角。你可以通过设置top、left、bottom和right偏移量属性来将元素移动到你想要的位置
通常情况下你并不希望元素相对于视口进行定位,而是相对于容器元素。在这种情况下,你需要为容器元素设置一个除了默认static之外的值。
由于给一个元素设置position: relative并不会将其从正常流中移除,所以通常这是一个不错的选择。给你想要相对的容器元素设置position : relative,就可以让绝对定位的元素相对其进行偏移。
.item{
width: 50px;
height: 50px;
background-color: pink;
position: absolute;
top: 20px;
left: 50px;
}
3.3. 固定定位(fixed positioning)
大多数情况下,position: fixed
的元素会相对于视口定位,并且会从正常文档流中被移除,不会保留它所占据的空间。当页面滚动时,固定的元素会留在相对于视口的位置,而其他正常流中的内容则和通常一样滚动。
当你想要一个固定导航栏一直停留在屏幕上时这会非常有效。和其他的position值一样,这也可能会造成一些元素被遮挡,需要小心保证页面内容的可读而不会被固定元素遮挡。
为了使一个固定定位的元素不相对于视口进行定位,你需要为容器元素设置transform、perspective、filter三个属性之一(不为默认值none)。这样固定的元素就会相对于该块级元素偏移,而非视口。
.item{
width: 50px;
height: 50px;
background-color: pink;
position: fixed;
top: 20px;
left: 30px;
}
3.4. STICKY 定位
设置position: sticky
会让元素在页面滚动时如同在正常流中,但当其滚动到相对于视口的某个特定位置时就会固定在屏幕上,如同fixed一般。这个属性值是一个较新的CSS属性,在浏览器兼容性上会差一些,但在不兼容的浏览器中会被忽略并会退到正常的滚动情况。
下面的代码展示了如何创建一个非常流行导航栏效果:导航栏会随着页面滚动,而当导航栏滚动到页面顶部时则会固定在顶部位置。
.item{
width: 50px;
height: 50px;
background-color: pink;
position: sticky;
top: 0;
width: 100%;
}
4. 弹性布局(Flex Layout)
弹性盒子(Flexbox)布局是一种为一维布局而设计的布局方法。一维的意思是你希望内容是按行或者列来布局。你可以使用display: flex
来将元素变为弹性布局。该容器的直接子元素会变为弹性项(flex item),并按行排列。
1
2
3
4.1. 弹性盒子的轴(axes)
在上面的例子中,我们会称弹性项在行内是从起始位置开始排列,而不是说它们是左对齐。这些元素会按行排列是因为默认的flex-direction值为row,row代表了文本的行文方向。由于我们工作的环境是英文(中文也是如此),一种自左向右的语言,行的开始位置就是在左边,因此我们的弹性项也是从左边开始的。因此flex-direction的值被定义为弹性盒子的主轴(main axis)。
交叉轴(cross axis)则是和主轴垂直的一条轴。如果你的flex-direction是row并且弹性项是按照行内方向排列的,那么交叉轴就是块级元素的排列方向。如果flex-direction是column那么弹性项就会以块级元素排列的方向排布,然后交叉轴就会变为row。设置flex-direction: column
效果如下:
4.2. 方向和次序
弹性盒子模型让我们可以通过为flex-direction属性设置row-reverse或column-reverse值来改变主轴上弹性项的方向。当然,你也可以通过order属性来改变某一个弹性项的顺序。但是要特别注意,这可能会给那些通过键盘(而非鼠标或触屏)访问你的网站的用户带来一些麻烦,因为tab的顺序是页面内元素在源码中的顺序而非显示顺序。设置flex-direction: row-reverse
效果如下:
4.3. 一些Flex的属性
这些flex的属性是用来控制弹性项在主轴上空间大小的。这三个属性是:
flex-grow / flex-shrink / flex-basis
通常可以使用它们的简写形式:flex: 1 1 200px
;第一个值代表flex-grow,第二个是flex-shrink,而第三个则是flex-basis。
flex-basis会为弹性项设置未拉伸和压缩时的初始大小。在上面的例子中,大小是200px,因此我们会给每个项200px的空间大小。但是大多数情况下容器元素大小不会正好被分为许多200px大小的项,而是可能有一些不足或剩余空间。flex-grow和flow-shrink属性允许我们在容器大小不足或有空余时控制各个弹性项的大小。
如果flex-grow的值是任意的正数,那么弹性项会被允许拉伸来占据更多的空间。因此,在上面的例子中,当各项被设为200px后,所有多余的空间会被每个弹性项平分并填满。
如果flex-shrink的值为任意的正数,那么当弹性项被设置了flex-basis后,元素溢出容器时会进行收缩。在上面这个CSS的例子中,如果容器空间不足,每个弹性项会等比例缩放以适应容器的大小。
flex-grow和flex-shrink的值可以是任意的正数。一个具有较大flex-grow值的弹性项会在容器有剩余空间时拉伸更大的比例;而一个具有更大flex-shrink值的项则会在容器空间不足时被压缩的更多。
.item {
flex: 1 1 200px;
width: 200px;
height: 50px;
padding: 10px;
background-color: rgba(111,41,97,.3);
border: 2px solid rgba(111,41,97,.5);
}
.container :nth-child(1) {
flex: 10 1 200px;
}
5. 网格布局(grid layout)
CSS网格布局(grid layout)是一种用来进行二维布局的技术。二维(two-dimesional)意味着你希望按照行和列来排布你的内容。和弹性盒子类似,网格布局也需要设置一个display值。你可以为容器元素设置display: grid
,并且使用grid-template-columns
和grid-template-rows
属性来控制网格中的行与列。
1
2
3
4
5
这段CSS会创建一个行列元素大小固定的网格。不过这也许并不是你希望的。默认值为auto,你可以认为这代表了“让格子尽可能的大”。如果你每没有指定行(row track)的大小,所有添加进来的行内容大小都会被置为auto。一种常用的模式是为网格制定列宽度,但是允许网格按需添加行。
你可以使用任意的长度单位或时百分比来设置行与列,同时你可以使用为网格系统所创造的新的单位——fr。fr是一种弹性单位,它可以指定网格容器内的空间被如何划分。
网格会替你计算与分配空间,你不需要去计算元素的百分比去适应容器大小。在下面这个例子中,我们使用fr来创建网格的列,这使得网格的列可以自适应。同时我们还使用了grid-gap
来保证元素间的间距(关于网格内元素与的间距会在“对齐”这一部分详细介绍)。
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
5.1. 关于网格的一些术语
网格系统总是有两个轴:inline axis
表示页面中文字的文字排列的方向,block axis
表示页面中块级元素的排列方向。
一个被设置为display: grid
的元素就是所谓的网格容器。在网格容器中会有网格线(grid line),网格线就是你在指定grid-template-columns
和grid-template-rows
时网格中行列所生成的。网格中的最小单位(也就是被四条网格线截取生成的区域)被成为网格单元格(grid cell),进一步的,由若干个单元格组成的矩形区域被成为网格区域(grid area)。
5.2. 网格的自动排列规则
一旦你创建了网格,那么网格容器的直接子元素就会开始将它们自己一个一个地放置在网格的单元格中。子元素的放置是依据网格的自动排列规则(auto-placement rule)。这些规则确保了网格内元素是被安排在各个空的单元格中,而不会彼此遮盖。
网格中任何没有被进行定位的直接子元素都会根据自动排列规则进行放置。在下面这个列子中,我让每三个元素中的第一个占据两行,但仍然从起始行开始去自动排列(自动伸缩需元素需去除width&height
属性)。
.container :nth-child(1){
grid-row-end: span 2;
}
5.3. 基于行/列的基本定位方法
定位网格元素最简单的方式是使用基于行/列(line)的定位方法,只需告诉浏览器从哪一排到哪一排来进行合并。例如,如果你需要一个2*2的网格区域,你可以将指定元素从第一行开始到第三行、从第一列开始到第三列,这样就可以覆盖到四个单元格。
.item {
grid-column-start: 1;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 3;
}
可简写为:
.item {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
你也可以让网格项(grid item)占据同一个单元格。支持一些内容间会覆盖的设计。网格项会像通常网页中的元素那样叠起来,在html源码中下面的网格项会叠在其他元素上面。你仍然可以用z-index来控制它的堆叠顺序
.container :nth-child(1){
grid-column: 1 / 3;
grid-row: 2;
z-index: 100;
}
.container :nth-child(4){
grid-row: 1/3;
grid-column: 2;
background-color: rgba(11,41,97,.3);
}
5.4. 通过命名区域来定位元素
你可以通过命名区域(named areas)来定位网格中的元素。要是用这种方式,你需要给每个元素一个名字,然后通过grid-template-areas
属性的值来描述布局方式。
使用这种方式有几个需要注意的点。如果你想要合并一些单元格作为你的网格项,你需要重复元素的名字。网格区域需要能形成一个完整的矩形 —— 每个单元格都需要被填入一个值。如果你想要空出某些单元格,那就需要使用.这个值。例如在下面的CSS里我将最右下角的单元格留空。
grid-template-areas:
"a a b"
"a c c";
6. 显示顺序和文档顺序(visual and document order)
在文章的最开始,我建议你以从上到下的阅读顺序来组织你的文档顺序,这样会有助于可读性和CSS的布局方式。从我们关于弹性盒子和CSS网格的简短介绍来看,你可以发现用这些布局方法可能会极大地改变页面展示的元素在文档中的顺序。这可能会导致一个隐含的问题。
在一些非可视化的应用场景中,浏览器会遵循文档源码来进行使用。因此,屏幕阅读器会读取文档的顺序,此外使用键盘tab键来浏览的用户访问文档的顺序是基于源码的顺序,而不是元素展示的顺序。许多屏幕阅读器的用户并非完全失明,他们可能在使用屏幕阅读器的同时也能够看到这些元素在文档的哪个部分。在这些情况下,当与源码进行对比时,这种混乱的页面展现可能会令人充满迷惑。
当你改变了元素在文档中原来的顺序时,一定确保知道自己在做什么。如果你发现你自己正在CSS中重新排序你的元素,你应该去回头看看是否要重新组织你的页面元素。你可以通过使用tab访问来测试一下你的页面。
7. 盒模型的生成(box generation)
你写在网页里的任何东西都会生成一个盒子(box),这篇文章讨论的所有东西其实都是如何能够使用CSS来按照你的设计布局这些盒子。然而,在某些情况下,你可能根本不想创建一个盒子。有两个display的属性值会帮你处理这种情况。
7.1. 不生成盒子或内容(display: none)
如果你希望元素以及它所有的内容(包括所有子元素)都不会生成,你可以使用display: none
。这样元素就不会被展示,并且不会保留其本该占有的空间。
7.2 不生成该元素,但是生成其所有子元素(display: contents)
display: content是display的一个新的属性值。为一个元素应用display: content
属性会导致其自身的盒子不生成但所有的子元素都会照常生成。这有什么用呢?试想一下,如果你希望一个弹性布局或网格布局中的非直接子元素能应用这些布局,这就会非常有用。
在下面这个例子里,第一个弹性项包含了两个子元素,由于它被设为display: contents
,它的盒子不会生成并且它的两个子元素会成为弹性项,并被当作弹性盒子容器的直接子元素来布局。
A
B
2
3
增加以下样式后:
.subitem {
width: 200px;
padding: 10px;
background-color: rgba(111,41,97,.3);
border: 2px solid rgba(111,41,97,.5);
}
.container .item:first-child{
display: contents;
}
8. 对齐
在以前,要实现对齐往往会用到一些很"tricky"的方式,并且能够使用的方法也非常有限。随着CSS盒模型对齐(box alignment module)的出现,这一切都发生了变化。你将会使用它来控制网格容器与弹性盒子容器中的对齐。未来其他的各种布局方法都会应用这些对齐属性。盒模型对齐(box alignment specification)规范中的一系列详细属性如下:
justify-content
align-content
place-content
justify-items
align-items
place-items
justify-self
align-self
place-self
row-gap
column-gap
gap
由于不同的布局模型有不同的特性,因此用于不同布局模型的对齐属性会有一些表现上的差异。让我们来看看在一些简单的网格与弹性布局中对齐是如何工作的。
align-items
和justify-items
属性相对是align-self
和justify-self
属性的一种批量形式。这些属性会控制与元素在其网格区域(grid area)中的对齐情况。
1
2
3
4
5
align-content
和justify-content
属性则会对网格中的行/列(tracks)进行对齐控制(网格容器中需要在排列完行/列元素后有多余的空间)。
align-content: space-between;
justify-content: end;
在弹性盒子中,align-items
和align-self
用来解决交叉轴上的对齐问题,而justify-content
则用于解决主轴上空间的分配。
1
2
3
在交叉轴上,把弹性行(flex line)和额外空间包裹在弹性容器中之后,你就可以使用align-content
了。
8.1. 行/列的间隔
一个多栏布局具有column-gap
属性,到目前位置,网格布局具有grid-column-gap
、grid-row-gap
和grid-gap
。这些现在都被从grid标准中删除而被添加进盒模型对齐中了。与此同时,grid-的前缀属性被重命名为column-gap
、row-gap
和gap
。浏览器会将带有前缀的属性换为新的重命名属性,所以如果你在目前的代码中使用兼容性更好的老名字也不用担心。
重命名意味着这些属性也能被应用于其他布局方法,一个明显的备选就是弹性盒子。虽然目前没有浏览器支持盒子模型中的gap属性,但是在未来我们应该可以使用column-gap
和row-gap
来创建弹性项目元素间的间距。
9. 多栏布局(多列布局)
多栏布局(multi-column layout)是一种支持创建多栏的布局类型,如同报纸上那样。每一块都被分割成栏(column),你会按照块方向在栏中往下读然后会在回到下一栏的顶部。然而用这种方式阅读在网页内容中并不总是有效,因为人们并不想去让滚动条滚动来、滚动去地去阅读。当需要展示少部分内容、折叠一组复选框或者其他一些小的UI组件时会非常有用。
当展示一组高度不同的卡片或产品时多栏布局也非常有用。
9.1. 设置栏的宽度
要设置一个最有的栏宽,并通知浏览器依此宽度展示尽可能多的栏可以使用下面的CSS:
.container {
column-width: 300px;
}
这会创建尽可能多的300px的栏,所有剩下的空间会被所有栏共享。因此,除非空间被划分为300px时没有剩余,否则你的栏会比300px稍多一些。
9.2. 设置栏的数目
除了设置宽度,你可以使用column-count来设置栏的数目。在这种情况下,浏览器会将空间均分给你需要的数目的栏。
.container {
column-count: 3;
}
如果你同时添加了column-width
和column-count
,那么column-count
属性会作为一个最大值限制。在下面的代码里,栏会被添加直到达到三个,此时任何额外的空间都会被分给三栏,即使空间足够成为一个额外的新栏。
.container {
column-width: 300px;
column-count: 3;
}
9.3. 间距和栏规则
你无法为单个栏盒子添加外边距和内边距,需要用column-gap
属性来设置间距。如果你不具体指定column-gap
的值,它会默认为1em
来防止栏间碰撞。这和其他布局方法中column-gap
的行为不一样,其他布局中默认为0。你可以在间距上使用任意的长度单元,包括0(如果你不希望有栏间距)。
column-rule
属性让你有能力向两栏间*添加规则。它是column-rule-width
、column-rule-color
和column-rule-style
的简写形式,可border行为类似。注意,一个规则自身不会占用任何空间。它会占据在间距的顶部,从而增加或减少那些你设置column-gap
的规则与内容间的空间。
1
2
3
33
9.4. 允许元素横跨多栏
你可以使用column-span
属性让多栏容器内的元素横跨多栏,类似通栏。
当column-span出现时,多栏容器分栏会在这个元素上放停下,因此,容器里的内容会在元素上方形成多栏样式,然后在横跨元素(spanning element)的下方形成一组新的栏盒子(column box)。
1
2
3
33
header
2
3
33
你只可以使用column-span: all
或column-span: none
,并不能让元素横跨某几个栏(非通栏)。在文章写作时,Firefox还不支持column-span属性。
10. 碎片化(Fragmentation)
多栏布局是碎片化(fragmentation)的一个例子,页面内容会被拆分成栏。这和打印时内容被分到不同页非常类似。这个过程是碎片化规范(Fragmentation specification)处理的。这个规范包括了一些帮助控制内容切分的属性。
11. 布局类型选择
大多数的网页会混合使用多种布局类型。各布局规范都准确定义了它们之间是如何相互作用的。例如,你可能会在网格布局的网格项中使用弹性布局。一些弹性容器可能具有定位属性或浮动。这些规范根据最优的布局方式已经包含了布局模型的混合使用。在这篇指南中,我尝试概述了这种布局类型的基本使用方式,来帮助你了解实现一个效果可能的最好方法。
然而,别害怕去运用多种方式来实现布局设计。担心你的选择会不会造成实际问题的情况比你想象中要少很多。所以请在开始就组织好你的文档结构,并且注意你文档内容的可视展示顺序。剩下的大部分工作就是在浏览器中试试你的布局方式是否符合预期。
原文链接:https://juejin.cn/post/6844903634849759239