深入解析css
摘要:重新学下css,学完了能很快做出东西,而且是看的见东西
Table of Contents
1. 层叠,优先级和继承
1.1 层叠
css样式是声明式的,也存在着结构概念。这背后有很多的问题要讨论,首先我们需要理解浏览器如何解析样式规则。每条规则单独来看很简
单,但是当两条规则提供了冲突的样式时会发生什么呢?也就是说对同一个元素使用多个规则会出现冲突
<!-- listing-1.2.html -->
<!doctype html>
<head>
<style>
h1 {
font-family: serif;
}
#page-title {
font-family: sans-serif;
}
.title {
font-family: monospace;
}
</style>
</head>
<body>
<header class="page-header">
<h1 id="page-title" class="title">
Wombat Coffee Roasters
</h1>
<nav>
<ul id="main-nav" class="nav">
<li><a href="/">Home</a></li>
<li><a href="/coffees">Coffees</a></li>
<li><a href="/brewers">Brewers</a></li>
<li><a href="/specials" class="featured">Specials</a></li>
</ul>
</nav>
</header>
</body>
最终ID选择器生效,层叠指的就是这一系列规则。它决定了如何解决冲突,是CSS语言的基础。
层叠的规则:
- 样式表的来源:样式是从哪里来的,包括你的样式和浏览器默认样式等.
- 使用优先级更高的声明,作者样式大于默认样式
- 选择器优先级:哪些选择器比另一些选择器更重要.
- 使用更高优先级的声明,是不是内联样式,使用内联样式
- 源码顺序:样式在样式表里的声明顺序.
术语解释
描述问题时候尽量加上html属性和css属性,避免混淆
选择器和生命块组成了一个规则集
body {
color: back;
font-family: Helvetica;
}
1.1.1 样式表的来源
- 程序员编写的样式表属于作者样式表,可以覆盖浏览器默认样式
- 用户代理样式表=浏览器默认样式,用户代理样式表优先级低
正如上面示例代码1.2所示,这个css主要是修饰h1元素的,然后css代码listing-1.2.html中作者样式覆盖了原先的默认样式
!important声明,标记了!important的声明会被当作更高优先级的来源,它的优先级大于作者样式优先级
1.1.2 理解优先级
如果无法用来源解决冲突声明,浏览器会尝试检查它们的优先级。
行内样式
实际上行内元素属于“带作用域的”声明,它会覆盖任何来自样式表或者 < style > 标签的样式。行内样式没有选择器,因为它们直接作用于所在的元素。为了在样式表里覆盖行内声明,需要为声明添加!important,这样能将它提升到一个更高优先级的来源。但如果行内样式也被标记为!important,就无法覆盖它了。最好是只在样式表内用!important。将以上修改撤销,我们来看看更好的方式。!important作用更像是将样式级别提升到最高选择器优先级
如果设置相同属性,那么即使应用了两个选择器,那么ID选择器的样式会生效
#main-nav a { color: white; background-color: #13a4a4; // id选择器的样式会生效 padding: 5px; border-radius: 2px; text-decoration: none; } .featured { background-color: orange; // 类选择器不生效,优先级不高 }优先级的准确规则如下。
- id选择器 > 类选择器 > 标签选择器
- 如果选择器的ID数量更多,则它会胜出(即它更明确)。
- 如果ID数量一致,那么拥有最多类的选择器胜出。
- 如果以上两次比较都一致,那么拥有最多标签名的选择器胜出。
比如下面例子,判断哪些属性会生效
html body header h1 { ←---- ❶ 4个标签 color: blue; } body header.page-header h1 { ←---- ❷ 3个标签和1个类 color: orange; } .page-header .title { ←---- ❸ 2个类 color: green; } #page-title { ←---- ❹ 1个ID color: red; }
4的id选择器优先级最高,因此标题是红色,3有两个类选择器,删除4,就会展示3的样式,两个类选择器比一个类选择器更明确说明 伪类选择器(如:hover)和属性选择器(如[type=”input”])与一个类选择器的优先级相同。通用选择器(*)和组合器(>、+、~)对优先级没有影响。
如果你在CSS里写了一个声明,但是没有生效,一般是因为被更高优先级的规则覆盖了。很多时候开发人员使用ID选择器,却不知道它会创建更高的优先级,之后就很难覆盖它。如果要覆盖一个ID选择器的样式,就必须要用另一个ID选择器。
优先级标记
也可以说是优先级表达式,选择器#page-header #page-title有2个ID,所以是[2.0.0],如果加上行内样式则就是[1.2.0.0]
关于优先级的思考
1.1.3 源码顺序
如果两个声明所影响的元素相同和优先级相同,那么源码(html的源码,不是样式源码)后出现的就会起作用
a.featured {
background-color: orange;
}
但是这样的写法,会出现如果其他位置也有a标签和featured的类属性,那么就会渲染到其他地方,所以要设计好html元素和选择器
- 链接样式和源码顺序
正如之前所说,在CSS中最好的答案通常是“这得看情况”。实现相同的
效果有很多途径。多想些实现方法,并思考每一种方法的利弊,这是很
有价值的。面对一个样式问题时,我经常分两个步骤来解决它。首先确
定哪些声明可以实现效果。其次,思考可以用哪些选择器结构,然后选
择最符合需求的那个。
<!doctype html>
<head>
<style>
a:link {
color: blue;
text-decoration: none;
}
a:visited {
color: purple;
}
a:hover {
text-decoration: underline;
}
a:active {
color: red;
}
h1 {
color: #2f4f4f;
margin-bottom: 10px;
}
.nav {
margin-top: 10px;
list-style: none;
padding-left: 0;
}
.nav li {
display: inline-block;
}
.nav a {
color: white;
background-color: #13a4a4;
padding: 5px;
border-radius: 2px;
text-decoration: none;
}
.nav .featured {
background-color: orange;
}
</style>
</head>
<body>
<header class="page-header">
<h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
<nav>
<ul id="main-nav" class="nav">
<li><a href="/">Home</a></li>
<li><a href="/coffees">Coffees</a></li>
<li><a href="/brewers">Brewers</a></li>
<li><a href="/specials" class="featured">Specials</a></li>
</ul>
</nav>
</header>
</body>
书写顺序之所以很重要,是因为层叠。优先级相同时,后出现的样式会覆盖先出现的样式。如果一个元素同时处于两个或者更多状态,最后一个状态就能覆盖其他状态。如果用户将鼠标悬停在一个访问过的链接上,悬停效果会生效。如果用户在鼠标悬停时激活了链接(即点击了它),激活的样式会生效。
这个顺序的记忆口诀是“LoVe/HAte”(“爱/恨”),即link(链接)、visited(访问)、hover(悬停)、active(激活)。注意,如果将一个选择器的优先级改得跟其他的选择器不一样,这个规则就会遭到破坏,可能会带来意想不到的结果。
- 层叠值
浏览器遵循三个步骤,即来源、优先级、源码顺序,来解析网页上每个元素的每个属性。在 CSS 中指的是多个样式规则对同一个元素的样式属性进行规定时,会发生的覆盖和继承的现象。每个样式规则都有一个权值,样式规则的权值越大,则该规则对元素的样式属性的影响越大。当多个样式规则同时作用于同一个元素时,系统会根据规则的权值进行排序,将权值大的规则应用到元素上,而权值小的规则会被忽略
1.1.4 两个经验法则
- 在选择器中不要使用ID。就算只用一个ID,也会大幅提升优先级
- 不要使用!important。它比ID更难覆盖,一旦用了它,想要覆盖原先的声明,就需要再加上一个!important,而且依然要处理优先级的问题。
- 关于重要性的一个重要提醒当创建一个用于分发的JavaScript模块(比如NPM包)时,强烈建议尽量不要在JavaScript里使用行内样式。如果这样做了,就是在强迫使用该包的开发人员要么全盘接受包里的样式,要么给每个想修改的属性加上!important
1.1.5 选择器种类
| 选择器 | 例子 | 例子描述 |
|---|---|---|
| .class | .intro | 选择 class=”intro” 的所有元素。 |
| .class1.class2 | .name1.name2 | 选择 class 属性中同时有 name1 和 name2 的所有元素。 |
| .class1 .class2 | .name1 .name2 | 选择作为类名 name1 元素后代的所有类名 name2 元素。 |
| #id | #firstname | 选择 id=”firstname” 的元素。 |
| * | * | 选择所有元素。 |
| element | p | 选择所有 元素。 |
| element.class | p.intro | 选择 class=”intro” 的所有 元素。 |
| element,element | div, p | 选择所有 元素和所有 元素。 |
| element element | div p | 选择 元素内的所有 元素。 |
| element>element | div > p | 选择父元素是 的所有 元素。 |
| element+element | div + p | 选择紧跟 元素的首个 元素。 |
| element1~element2 | p ~ ul | 选择前面有 元素的每个
|
| [attribute] | [target] | 选择带有 target 属性的所有元素。 |
| [attribute=value] | [target=_blank] | 选择带有 target=”_blank” 属性的所有元素。 |
| [attribute~=value] | [title~=flower] | 选择 title 属性包含单词 “flower” 的所有元素。 |
| [attribute | =value] | [lang |
| [attribute^=value] | a[href^=”https”] | 选择其 src 属性值以 “https” 开头的每个 元素。 |
| [attribute$=value] | a[href$=”.pdf”] | 选择其 src 属性以 “.pdf” 结尾的所有 元素。 |
| [attribute*=value] | a[href*=”w3schools”] | 选择其 href 属性值中包含 “abc” 子串的每个 元素。 |
| :active | a:active | 选择活动链接。 |
| ::after | p::after | 在每个 的内容之后插入内容。 |
| ::before | p::before | 在每个 的内容之前插入内容。 |
| :checked | input:checked | 选择每个被选中的 元素。 |
| :default | input:default | 选择默认的 元素。 |
| :disabled | input:disabled | 选择每个被禁用的 元素。 |
| :empty | p:empty | 选择没有子元素的每个 元素(包括文本节点)。 |
| :enabled | input:enabled | 选择每个启用的 元素。 |
| :first-child | p:first-child | 选择属于父元素的第一个子元素的每个 元素。 |
| ::first-letter | p::first-letter | 选择每个 元素的首字母。 |
| ::first-line | p::first-line | 选择每个 元素的首行。 |
| :first-of-type | p:first-of-type | 选择属于其父元素的首个 元素的每个 元素。 |
| :focus | input:focus | 选择获得焦点的 input 元素。 |
| :fullscreen | :fullscreen | 选择处于全屏模式的元素。 |
| :hover | a:hover | 选择鼠标指针位于其上的链接。 |
| :in-range | input:in-range | 选择其值在指定范围内的 input 元素。 |
| :indeterminate | input:indeterminate | 选择处于不确定状态的 input 元素。 |
| :invalid | input:invalid | 选择具有无效值的所有 input 元素。 |
| :lang(language) | p:lang(it) | 选择 lang 属性等于 “it”(意大利)的每个 元素。 |
| :last-child | p:last-child | 选择属于其父元素最后一个子元素每个 元素。 |
| :last-of-type | p:last-of-type | 选择属于其父元素的最后 元素的每个 元素。 |
| :link | a:link | 选择所有未访问过的链接。 |
| :not(selector) | :not(p) | 选择非 元素的每个元素。 |
| :nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每个 元素。 |
| :nth-last-child(n) | p:nth-last-child(2) | 同上,从最后一个子元素开始计数。 |
| :nth-of-type(n) | p:nth-of-type(2) | 选择属于其父元素第二个 元素的每个 元素。 |
| :nth-last-of-type(n) | p:nth-last-of-type(2) | 同上,但是从最后一个子元素开始计数。 |
| :only-of-type | p:only-of-type | 选择属于其父元素唯一的 元素的每个 元素。 |
| :only-child | p:only-child | 选择属于其父元素的唯一子元素的每个 元素。 |
| :optional | input:optional | 选择不带 “required” 属性的 input 元素。 |
| :out-of-range | input:out-of-range | 选择值超出指定范围的 input 元素。 |
| ::placeholder | input::placeholder | 选择已规定 “placeholder” 属性的 input 元素。 |
| :read-only | input:read-only | 选择已规定 “readonly” 属性的 input 元素。 |
| :read-write | input:read-write | 选择未规定 “readonly” 属性的 input 元素。 |
| :required | input:required | 选择已规定 “required” 属性的 input 元素。 |
| :root | :root | 选择文档的根元素。 |
| ::selection | ::selection | 选择用户已选取的元素部分。 |
| :target | #news:target | 选择当前活动的 #news 元素。 |
| :valid | input:valid | 选择带有有效值的所有 input 元素。 |
| :visited | a:visited | 选择所有已访问的链接。 |
1.2 继承
某些元素,在我们不指定属性值(没有层叠值)时候,他就会考虑从父标签中继承。但并不是所有的标签属性都会被进程,只有些特定的。主要是跟文本相关的属性会被继承
color
font
font-family
font-size
font-weight
font-variant
font-style
line-height
letter-spacing
text-align
text-indent
text-transform
white-space
word-spacing
list-style、list-style-type、list-style-position以及list-style-image。表格的边框属性border-collapse和border-spacing也能被继承。注意,这些属性控制的是表格的边框行为,而不是常用于指定非表格元素边框的属性
代码1-13
在body元素上修改了字体属性,子元素如果没有修改对应元素,那么就会继承body元素中的关于字体的定义
1.3 特殊值
有两个特殊值可以赋给任意属性,用于控制层叠:inherit和 initial。
1.3.1 使用inherit
通常会给页面的所有链接的字体加一个醒目的蓝色,但是有个有需求说要让页脚的链接字体跟页脚一个颜色。那么我们就可以使用继承就可以解决问题
1.3.2 initial关键字
每一个CSS属性都有初始(默认)值。如果将initial值赋给某个属性,那么就会有效地将其重置为默认值,这种操作相当于硬复位了该值。这么做的好处是不需要思考太多。如果想删除一个元素的边框,设置border: initial即可。如果想让一个元素恢复到默认宽度,设置width: initial即可。
正如代码1-15所以,如果不指定inherit的话,那么也就会使用后面的样式值,因为它的样式表达式值权重更高
auto不是所有属性的默认值,对很多属性来说甚至不是合法的值。比如border-width: auto和padding: auto是非法的,因此不会生效。可以花点时间研究一下这些属性的初始值,不过使用initial更简单。
1.4 简写属性
比如font: italic bold 18px/1.2 "Helvetica", "Arial", sans-serif;就指定了font-style、font-weight、font-size、font-height以及font-family
更多的还有
- background是多个背景属性的简写属性:background-color、background-image、background-size、background-repeat、background-position、background-origin、background-chip以及background-attachment。
- border是border-width、border-style以及border-color的简写属性,而这几个属性也都是简写属性。
- border-width是上、右、下、左四个边框宽度的简写属性。
简写属性会设置省略值为其初始值
title {
font: 32px Helvetica, Arial, sans-serif;
}
代码展开来写就是
h1 {
font-weight: bold;
}
.title {
font-style: normal;
font-variant: normal;
font-weight: normal;
font-stretch: normal;
line-height: normal;
font-size: 32px;
font-family: Helvetica, Arial, sans-serif;
}
在所有的简写属性里,font的问题最严重,因为它设置的属性值太多了。因此,要避免在
元素的通用样式以外使用font。当然,其他简写属性也可能会遇到一样的问题,因此要当心。1.4.1 理解简写样式的顺序
简写属性会尽量包容指定的属性值的顺序。可以设置border: 1px solid black或者border: black 1px solid,两者都会生效。这是因为浏览器知道宽度、颜色、边框样式分别对应什么类型的值
- 上、右、下、左
.
nav a {
color: white;
background-color: #13a4a4;
padding: 10px 15px 0 5px; ←---- 上、右、下、左内边距
border-radius: 2px;
text-decoration: none;
}
这种模式下的属性值还可以缩写。如果声明结束时四个属性值还剩一个没指定,没有指定的一边会取其对边的值。指定三个值时,左边和右边都会使用第二个值。指定两个值时,上边和下边会使用第一个值。如果只指定一个值,那么四个方向都会使用这个值。因此下面的声明都是等价的。
padding: 1em 2em;
padding: 1em 2em 1em;
padding: 1em 2em 1em 2em;
但是下面代码就是上,下,左,右
padding: 1em ;
- 水平、垂直
有些属性包括background-position、box-shadow、text-shadow,比如background-position: 25% 75%则先指定水平方向的右/左属性值,然后才是垂直方向的上/下属性值。
虽然看起来顺序相反的定义违背了直觉,原因却很简单:这两个值代表了一个笛卡儿网格。笛卡儿网格的测量值一般是按照 (水平,垂直)的顺序来的。比如,如图1-15所示,要给元素加上一个阴影,就要先指定 (水平)值。
.nav .featured {
background-color: orange;
box-shadow: 10px 2px #6f9090; ←---- 阴影向右偏移10px,向下偏移2px
}
如果属性需要指定从一个点出发的两个方向的值,就想想“笛卡儿网格”。如果属性需要指定一个元素四个方向的值,就想想”时钟”。
2. 相对单位
em的单位难以把握,像素单位相对简单
2.1 相对值的好处
2.1.1 那些年追求的像素级完美
2.1.2 像素级完美的时代终结了
响应式:根据浏览器大小有不同的响应
2.2 em和rem
em是一种计算单位,它的单位是根据所修饰元素的字体大小进行计算。
比如代码2-1
如上代码设置的16px,那么padding的内边距也是16px,那么如果你的字体是32px,那么内边距就会是32px.
当计算padding,heigh,width,border-radius.使用em会非常方便。这因为当元素的字体改变时就会更改em的单位。
2.2.1 使用em定义字号
如果定义字体的的单位是em,那么那么实际上这个字体的结果是从继承的字号进行计算的。
如代码2-5所展示的
也就是 继承父元素字号*em = 子元素像素值,那么也就是css中的em单位是子除父字号,大多数浏览器来说,默认的字号为16px。
- em同时作用于字体和其他属性
浏览器要先计算字号,然后再计算,然后再根据这个结算后的字号值,再去计算其他值
- 字体缩小的问题
想象这一种场景,我们说em就是一种权重,那么如果DOM中是嵌套的,然后子元素的字体大小通过继承父元素大小计算。如果em是小于0的值,就会变成子元素逐级变小。
代码2-9 展示出用选择器优先级来覆盖我们碰到的问题。ul ul选择出所有的ul元素
2.2.2 使用rem设置字号
当浏览器解析HTML文档时,会在内存里将页面的所有元素表示为DOM(文档对象模型)。它是一个树结构,其中每个元素都由一个节点表示。< html >元素是顶级(根)节点。它下面是子节点,< head >和< body >。再下面是逐级嵌套的后代节点。在文档中,根节点是所有其他元素的祖先节点。根节点有一个伪类选择器:root,可以用来选中它自己。这等价于类型选择器html {}
rem是root em的缩写,rem不是相对于当前元素,还是相对根元素,不管其他元素位于网页的任何位置,都只是计算根元素的字体大小代码清单2-10
在CSS里,设置位置通常是“看情况”。rem只是你工具包中的一种工具。掌握CSS很重要的一点是学会在适当的场景使用适当的工具。我一般会用rem设置字号,用px设置边框,用em设置其他大部分属性,尤其是内边距、外边距和圆角(不过我有时用百分比设置容器宽度)。
2.3停止像素思考
很很多程序员会设置html标签字体的大小。这也的确能计算子元素的相对值,但是这回有2种弊端
- 这会写很多覆盖的代码
- 这本质上还是像素思想,这样不利于写响应式网页
自适应布局:是指网页能够在不同大小的终端设备上自行适应显示,也就是让一个网站在不同大小的设备上显示同一样的页面,让同一个页面适应不同大小屏幕,根据屏幕的大小,自动缩放。自适应布局的几个标志:
- 比如单终端(手机)的N主流产品
- 当网页缩写到一定程度时,界面会出现显示不全,并且出现横向滑动条;
- 总体框架不变,横线布局的版块太多会有所减少。
响应式布局: 就是一个网站能够兼容多个终端,可以根据屏幕的大小自动调整页面的的展示方式以及布局,我们不用为每一个终端做一个特定的版本。响应式网站的几个标志:
- 同时适配PC + 平板 + 手机等;
- 标签导航在接近手持终端设备时改变为经典的抽屉式导航;
- 网站的布局会根据视口来调整模块的大小和位置;
2.3.1 设置一个合理的默认的字号
在写css样式代码中,不应该采用新的字号值覆盖父值的办法,这样效率非常低下,要采用相对单位来实现字体大小的调整。可设置root元素的字体值然后在其基础上进行计算。
:root {
font-size:0.875em
}
--公式是父/子=em
在代码2-14中使用了em设置边距和圆角,使用rem设置标题和字号
2.3.2 构建响应式面板
在构建响应式web之前需要设置响应式面板,可以根据不同媒体媒介进行设置。及@media可以指定屏幕尺寸和媒体类型。
:root {
font-size: 0.75em; (以下3行)作用到所有的屏幕,但是在大屏上会被覆盖
}
@media (min-width: 800px) { (以下5行)仅作用到宽度800px及其以上的屏幕,覆盖之前的值
:root {
font-size: 0.875em;
}
}
@media (min-width: 1200px) { (以下5行)仅作用到宽度1200px及其以上的屏幕,覆盖前面两个值
:root {
font-size: 1em;
}
}
上卖弄代码不难看出,第一个规则集在大屏幕会被覆盖,当屏幕大于800px然后第二个规则集会生效。大于1200px则第三个规则会生效
具体代码如2-15
2.3.3 缩放单个组件
假如我们有个需求是对屏幕中某个组件进行缩放,那么我们可以采用覆盖值的但是正如代码2-16所示。
采用高权重样式值覆盖的策略
2.4 视口的相对单位
- vh:视口高度的1/100。
- vw:视口宽度的1/100。
- vmin:视口宽、高中较小的一方的1/100(IE9中叫vm,而不是vmin)。
- vmax:视口宽、高中较大的一方的1/100(本书写作时IE和Edge均不支持vmax
2.4.1 使用vw定义字号
如果给一个元素加上font-size: 2vw会发生什么?在一个1200px的桌面显示器上,计算值为24px(1200的2%)。在一个768px宽的平板上,计算值约为15px(768的2%)。这样做的好处在于元素能够在这两种大小之间平滑地过渡,这意味着不会在某个断点突然改变。当视口大小改变时,元素会逐渐过渡。
2.4.2 使用calc()定义字号
calc()函数内可以对两个及其以上的值进行基本运算。当要结合不同单位的值时,calc()特别实用。它支持的运算包括:加(+)、减(-)、乘(×)、除(÷)。加号和减号两边必须有空白,因此我建议大家养成在每个操作符前后都加上一个空格的习惯,比如calc(1em +10px)。代码清单2-19用calc()结合了em和vw两种单位。删除之前样式表的基础字号(以及相关的媒体查询),换成如下代码。代码2-19,现在打开网页,慢慢缩放浏览器,字体会平滑地缩放。0.5em保证了最
小字号,1vw则确保了字体会随着视口缩放。这段代码保证基础字号从iPhone 6里的11.75px一直过渡到1200px的浏览器窗口里的20px。可以按照自己的喜好调整这个值。我们不用媒体查询就实现了大部分的响应式策略。省掉三四个硬编码的断点,网页上的内容也能根据视口流畅地缩放。
2.5 无单位的数值和行高
- 如果父级的line-height属性值有单位或百分比,那么子级继承的值则是换算后的一个具体的px级别的值;
- 而如果父级的line-height属性值没有单位,则子级会直接继承这个“数值”,而非计算后的具体值,此时子级的line-height会根据本身的font-size值重新计算得到新的line-height值。
每个元素使用相同的font-size,但使用不同的font-family,但渲染出来的line-height是不同的。
CSS 权威指南基本视觉格式化一章中讲到:对于行内非替换元素或者匿名文本来说, font-size 指定了它们的 content area的高度,由于inline box 是由 content area 加上上下的 half-leading构成的,那么如果元素的leading为 0,在这种情况下,font-size 指定了inline box 的高度。
英文字体有基线(baseline)和中线(meanline),这两条线之间就是所谓的x-height,即小写字母x的高度。基线之上的部分是上伸区域(ascent),基线之下的部分是下伸区域(descent).
2.6 自定义属性(即css变量)
在2015年新的css规范引入了层叠变量的自定义属性。也就是允许在css使用变量的概念。
:root {
--main-font: Helvetica, Arial, sans-serif;
}
这相当于定义了一个全局的,在整个网页都可以使用的变量main-font,如果是在根节点定义变量,那么它下面的子元素都可以使用该变量,同理如果在某一节点元素内定义变量,那么它下面的子元素才可以使用,兄弟元素和它的父元素都无法使用,然后我们可以调用var()函数取出定义的值。
p {
font-family: var(--main-font);
color: var(--brand-color);
}
思考一下如果第一个值未定义,那么就可以使用备用值.
p {
font-family: var(--main-font, sans-serif); ←---- 指定备用值为sans-serif
color: var(--secondary-color, blue); ←---- secondary-color变量没有定义,因此会使
}
如果定义的变量是个非法值就会使用属性的默认值
2.6.1 动态改变自定义属性
其实这节主要就是讲解自定义属性变量的作用域。有如下例子
<!doctype html>
<head>
<style>
:root {
font-size: calc(0.5em + 1vw);
--main-bg: #fff;
--main-color: #000;
}
body {
font-family: Helvetica, Arial, sans-serif;
}
.dark {
margin-top: 2em;
padding: 1em;
background-color: #999;
--main-bg: #333;
--main-color: #fff;
}
.panel {
font-size: 1rem;
padding: 1em;
border: 1px solid #999;
border-radius: 0.5em;
background-color: var(--main-bg);
color: var(--main-color);
}
.panel > h2 {
margin-top: 0;
font-size: 0.8em;
font-weight: bold;
text-transform: uppercase;
}
.panel.large {
font-size: 1.2em;
}
</style>
</head>
<body>
<div class="panel">
<h2>Single-origin</h2>
<div class="body">
We have built partnerships with small farms
around the world to hand-select beans at the
peak of season. We then carefully roast in
small batches to maximize their potential.
</div>
</div>
<aside class="dark">
<div class="panel">
<h2>Single-origin</h2>
<div class="body">
We have built partnerships with small farms
around the world to hand-select beans at the
peak of season. We then carefully roast in
small batches to maximize their potential.
</div>
</div>
</aside>
</body>
在上面例子中定义了两个属性。第一个在根属性上定义了属性,第二个在dark元素上定义了属性。但是第二个属性会覆盖掉第一个全局的属性,根语言中属性作用域很像
2.6.2 使用js改变自定义属性
具体实现不需要深究,只需要知道js可以改变属性的值,从而使网页设计更灵活
2.6.3 探索自定义属性
自定义属性是CSS中一个全新的领域,开发人员刚刚开始探索。因为浏览器支持有限,所以还没有出现“典型”的用法。我相信假以时日,会出现各种最佳实践和新的用法。这需要你持续关注。继续使用自定义属性,看看能用它做出什么效果。值得注意的是,在不支持自定义属性的浏览器上,任何使用var()的声明都会被忽略。请尽量为这些浏览器提供回退方案。然而这种做法不是万能的,比如当用到自定义属性的动态特性时,就很难有备用方案。关注 Can I Use网站,查看最新的浏览器支持情况。
3 盒子模型
3.1 元素宽度的问题
这一节最重要的是盒子模型

代码3-3设置的windth=30%为内容的宽度。在以上例子中(代码清单3-3),两列并没有并排出现,而是折行显示。虽然将两列宽度设置为70%和30%,但它们总共占据的宽度超过了可用空间的100%,侧边栏的宽度等于30%宽度加上各1.5em的左右内边距,主容器的宽度只占70%。两列宽度加起来等于100%宽度加上3em。因为放不下,所以两列便折行显示了。
3.1.1 避免魔术数值
替代魔术数值的一个方法是让浏览器帮忙计算。在本例中,因为加了内边距,两列的宽度总和超出了3em,所以可以使用calc()函数减去这个值,得到刚好100%的总和。比如设置侧边栏宽度为calc(30% - 3em)就能刚好并排放下两列,但是还有更好的解决办法。
3.1.2 调整盒子模型
默认的盒子模型的宽度的width是内容的宽度,box-sizing: border-box的盒子模型的width的宽度是边框和内容
3.1.3 全局设置border-box
*,::before,::after {
box-sizing: border-box; ←---- 给页面上所有元素和伪元素设置border-box
}
全部元素都采用border-box属性,但是引入第三方的css代码就会导致无法影响别人的代码,故采用
:root {
box-sizing: border-box; ←---- 根元素设置为border-box
}
*,
::before,
::after {
box-sizing: inherit; ←---- 告诉其他所有元素和伪元素继承其盒模型
}
.third-party-component {
box-sizing: content-box;
}
这样就解决了潜在影响第三方库css代码(该书后面代码都是用box-sizing:border-box)
3.1.4 给列之间加上间隔
实现在两个元素之间添加间距的方法有很多,
- 简单的就是通过计算width的值,在值上trade-off,但是间隔的宽度由外层容器的宽度决定.用em指定间距,因为em单位的一致性更好。
- 使用calc()函数,如代码3-8
3.2 元素高度的问题
普通文档流——指的是网页元素的默认布局行为。行内元素跟随文字的方向从左到右排列,当到达容器边缘时会换行。块级元素会占据完整的一行,前后都有换行。
3.2.1 控制溢出行为
当明确设置一个元素的高度时,内容可能会溢出容器。当内容在限定区域放不下,渲染到父元素外面时,就会发生这种现象
- visible(默认值)——所有内容可见,即使溢出容器边缘。
- hidden——溢出容器内边距边缘的内容被裁剪,无法看见。
- scroll——容器出现滚动条,用户可以通过滚动查看剩余内容。在一些操作系统上,会出现水平和垂直两种滚动条,即使所有内容都可见(不溢出)。不过,在这种情况下,滚动条不可滚动(置灰)。
- auto——只有内容溢出时容器才会出现滚动条。
请谨慎地使用滚动条。浏览器给网页最外层加上了滚动条,如果网页内部再嵌套滚动区域,用户就会很反感。如果用户使用鼠标滚轮滚动网页,当鼠标到达一个较小的滚动区域,滚轮就会停止滚动网页,转而滚动较小的区域。
水平方向的溢出除了垂直溢出,内容也可能在水平方向溢出。一个典型的场景就是在一个很窄的容器中放一条很长的URL。溢出的规则跟垂直方向上的一致。可以用overflow-x属性单独控制水平方向的溢出,或者用overflow-y控制垂直方向溢出。这些属性支持overflow的所有值,然而同时给x和y指定不同的值,往往会产生难以预料的结果。
3.2.2 百分比高度的备选方案
在实现页面的时候,通常是子元素的高度决定了父元素的高度。这样就会一直往上依赖.要想百分高度生效,需要给父元素设置一个高度
- 等高列
在早期时候使用表格实现等高列但通常是通过内容来计算高度,现代浏览器解决这种问题. - css表格布局
代码3-10- 首先要给容器设置
display:table,然后给每一列设置display:table-cell
以上代码缺少间隔,这是因为外边距并不会因为table-cell元素,所以要修改代码,让间隔生效,可以使用table-spacing属性定义单元格的间距。该元素接收两个长度值,一个是水平间距一个是垂直间距。可以给容器加上border-spacing: 1.5em,0em ,但是这样有个副作用。就是左右没法对齐 。机制的你可能会想到父边距,然后我们在外面包上一层。然后指定左右负边距。.container { display: table; ←----让容器布局像表格一样 width: 100%; ←---- ❶让表格填充容器的宽度 } .main { display: table-cell; width: 70%; background-color: #fff; border-radius: .5em; } .sidebar { display: table-cell; width: 30%; margin-left: 1.5em; <-----外边距不在生效 padding: 1.5em; background-color: #fff; border-radius: .5em; }
- 首先要给容器设置
- Flexbox
可以使用Flexbox实现等高列,我们可以给容器设置flex,就变成了一个弹性容器详情参考代码3-11
3.2.3 使用min-height和max-height
max-height允许元素自然地增高到一个特定界限。如果到达这个界限,元素就不再增高,内容会溢出
3.2.4 垂直居中内容
对于显示为table-cell的元素,vertical-align控制了内容在单元格内的对齐。如果你的页面用了CSS表格布局,那么可以用vertical-align来实现垂直居中。具体详情 参考代码3-12
这个地方要在网上看看例子
3.3 负边距
如果设置左边或顶部的负外边距,元素就会相应地向左或向上移动,导致元素与它前面的元素重叠,如果设置右边或者底部的负外边距,并不会移动
元素,而是将它后面的元素拉过来。给元素底部加上负外边距并不等同于给它下面的元素顶部加上负外边距。
3.4 外边距折叠
鸡儿的,元素的外边距是折叠的,产生单个外边距。这种现象被称为折叠。
3.4.1 文字折叠
折叠外边距的大小等于相邻外边距中的最大值
3.4.2 多个外边距折叠
在代码清单3-13中,有三个不同的外边距折叠到一块了:< h2>底部的外边距、< div>顶部的外边距、< p>顶部的外边距。计算值分别是19.92px ,0px、16px。因此最终间隔还是19.92px,也就是三者中最大的值。实际上,即使将段落放在多个div中嵌套,渲染结果都一样:所有的外边距都会折叠到一起。总之,所有相邻的顶部和底部外边距会折叠到一起。如果在页面中添加一个空的、无样式的div(没有高度、边框和内边距),它自己的顶部和底部外边距就会折叠。
3.4.3 容器外部折叠
容器内部元素跑出外与别的元素外边距折叠。可考虑在容器设置内边界
还有如下方法防止外边距折叠
- 对容器使用overflow: auto(或者非visible的值),防止内部元素的外边距跟容器外部的外边距折叠。这种方式副作用最小。
- 在两个外边距之间加上边框或者内边距,防止它们折叠。
- 如果容器为浮动元素、内联块、绝对定位或固定定位时,外边距不会在它外面折叠。
- 当使用Flexbox布局时,弹性布局内的元素之间不会发生外边距折叠。网格布局(参见第6章)同理。
- 当元素显示为table-cell时不具备外边距属性,因此它们不会折叠。此外还有table-row和大部分其他表格显示类型,但不包括table、table-inline、table-caption。
3.5 容器内的元素间距
详情在代码3-17 绘制了一个间距相同的两个按钮
3.5.1 如果内容改变了
如果在代码3-17中重新添加一个元素我又要重新编写样式
3.5.2 更通用的方案:猫头鹰解决方案
Web设计师Heydon Pickering曾表示外边距“就像是给一个物体的一侧涂了胶水,而你还没有决定是否要将它贴到某处,或者还没想好要贴到什么东西上”。不要给网页当前的内容固定外边距,而是应该采取更通用的方式,不管网页结构如何变化都能够生效。这就是Heydon Pickering所说的迟钝的猫头鹰选择器(lobotomized owl selector)(以下简称猫头鹰选择器),因为它长这样:* + * 。该选择器开头是一个通用选择器(*),它可以选中所有元素,后面是一个相邻兄弟组合器(+),最后是另一个通用选择器。它因形似一只眼神空洞的猫头鹰而得名。猫头鹰选择器功能接近此前介绍的选择器:.social-button + .social-button,但是它不会选中直接跟在其他按钮后面的按钮,而是会选中直接跟在其他元素后面的任何元素。也就是说,它会选中页面上有着相同父级的非第一个子元素代码3-22
4 理解浮动
说白了主要有三种方式影响文档流
- 浮动
- Flexbox
- 网格布局
早些时候用的更多的是浮动布局,但是这种当时布局很难理解,在本章将会介绍双容器和媒体对象
4.1 浮动的设计初衷
浮动能将一个元素(通常是一张图片)拉到其容器的一侧,这样文档流就会包围它,也可以浮动到右侧,这时候文档流会重新排列,如果多个元素浮动,那么他们会挨着浮动。设计页面的时候通常是规划好外层容器,然后再设计内层。
双容器模式:通过将内容放置到两个嵌套的容器中,然后给内层的容器设置外边距,让它在外层容器中居中,在本例中,< body >就是外层容器。因为它默认是100%的网页宽度,所以不用给它添加新的样式。在< body >内部,整个网页的内容放在了< div class=”container” >,也就是内层容器中。对于内层容器,需要设置一个max-width,并将外边距设置为auto,使内容居中。将代码清单4-3添加到你的样式表中
4.2 容器折叠和清除浮动
4.2.1 理解容器折叠
这一节一定要从代码上加以理解。代码4-4
4.2.2 理解清除浮动
暂时还不理解
4.3 出乎意料的浮动陷阱
浮动布局先放一放
5 Flexbox
flexbox算是现代的网页布局方式
5.1 FLexbox的原则
display:flex就可以使容器变成一个弹性容器,它里面的元素就是弹性子元素把他想象成一个罐子,里面装东西,子元素按照主轴线排列,主轴的方向为主起点(左)到主终点(右)。垂直于主轴的是副轴。方向从副起点(上)到副终点(下)。下面我们看个实际应用例子5-1
我们还可以使用
display:inline-flex其行为更像是inline--bolck,但是其长度不会自动加到100%。
5.1.1 创建一个基础的Flexbox菜单
这一节主要是介绍了,在不同浏览器我们是要使用不同的属性名来使用flexbox的定义,我们可以使用Autoprefixer的工具进行格式化批量更改
- 旧版safari
主要还是要学习下相关示例代码5-3.site-nav { display: -webkit-flex; display: flex; }
5.1.2 添加内边距和间隔
这节我们我们围绕示例代码5-4
注意这里的链接被设置为块级元素。如果链接还是行内元素,那么它给父元素贡献的高度会根据行高计算,而不是根据内边距和内容,这样不符合预期
代码5-5教我们如何实现最后一个元素移到右面
如果希望菜单项等间距,那么justify-content属性会是更好的方式。
5.2 弹性子元素的大小
flexbox提供可比width设heigh功能更强大flex属性。flex控制主轴方向的大小,flex会被自动计算然后填满容器宽度
5.2.1 使用flex-basis属性
flex-basis定义了元素大小的基准值,即一个初始的“主尺寸”。flex-basis属性可以设置为任意的width值,包括px、em、百分比。它的初始值是auto,此时浏览器会检查元素是否设置了width属性值。如果有,则使用width 的值作为flex-basis的值;如果没有,则用元素内容自身的大小。如果flex-basis的值不是auto,width属性会被忽略
5.2.2 使用flex-grow属性
我们上面使用flex-basis属性会导致页面出现空白区域,但是我们可以使用flex-grow属性,来控制页面放缩时候的放缩权重,flex-grow值越大,子元素能分配剩余可用宽度的比例越大推荐使用简写属性flex,而不是分别声明flex-grow、flex-shrink、flex-basis。与大部分简写属性不一样,如果在flex中忽略某个子属性,那么子属性的值并不会被置为初始值。相反,如果某个子属性被省略,那么flex简写属性会给出有用的默认值:flex-grow为1、flex-shrink为1、flex-basis为0%。这些默认值正是大多数情况下所需要的值
5.2.3 使用flex-shrink属性
flex-shrink属性与flex-grow遵循相似的原则。计算出弹性子元素的初始主尺寸后,它们的累加值可能会超出弹性容器的可用宽度。如果不用flex-shrink,就会导致溢出,每个子元素的flex-shrink值代表了它是否应该收缩以防止溢出。如果某个子元素为flex-shrink: 0,则不会收缩;如果值大于0,则会收缩至不再溢出。按照 flex-shrink值的比例,值越大的元素收缩得越
多。
5.2.4 实际应用
重点是如何实现圣杯布局,众所周知,用CSS实现这种布局非常困难。该布局中,两个侧边栏宽度固定,而中间的列是“流动的”,即它会自动填充可用空间。重点是,三列的高度相等,该高度取决于它们的内容。尽管浮动也能实现这种布局,但需要用一些既晦涩又脆弱的技巧。你可以使用不同的弹性子元素,想出很多不同的方式来组合以上的布局。
5.3 弹性方向
如何切换主主副轴方向,可以使用flex-direction: column,指定flex-direction: column能控制弹性子元素沿垂直方向排列(从上到下)。Flexbox还支持row-reverse让元素从右到左排列,column-reverse让元素从下到上排列
5.3.1 改变弹性方向
.column-sidebar { (以下5行)对外面的弹性盒子来说是弹性子元素,对内部的元素而言是弹性容器
flex: 1;
display: flex;
flex-direction: column;
}
内部的弹性盒子的弹性方向为column,因此主轴发生了旋转,现在变成了从上到下(副轴变成了从左到右)。也就是对于弹性子元素而言,flex-basis、flex-grow和flex-shrink现在作用于元素的高度而不是宽度。
水平弹性盒子的大部分概念同样适用于垂直的弹性盒子(column或column-reverse),但是有一点不同:在CSS中处理高度的方式与处理宽度的方式在本质上不一样。弹性容器会占据100%的可用宽度,而高度则由自身的内容来决定。即使改变主轴方向,也不会影响这一本质。
5.3.2 登录表单的样式
详情请学习代码清单5-10
5.4 对齐,间距等细节
通常情况下,创建一个弹性盒子需要用到前面提及的这些方法。
- 选择一个容器及其子元素,给容器设置display: flex
- 如有必要,给容器设置flex-direction
- 给弹性子元素设置外边距和/或flex值,用来控制它们的大小
5.4.1 理解弹性容器的属性
| 属性 | 值 |
|---|---|
| flex-direction | row是水平方向 row-reverse是水平反方向 column是默认向下 column-reverse是主轴反过来 |
| flex-wrap折叠显示 | no-wrap就不会折叠 wrap是折叠 wrap-reverse是反向折叠。flex-wrap的叠着跟主轴的方向有关 |
| flex-flow | < flex-direction > < flex-wrap >的简写 |
| justify-content控制子元素在主轴上的位置 | flex-start flex-end center space-between space-around |
| align-items控制子元素在副轴上的位置 | flex-start flex-end center stretch baseline |
| align-content如果开启了flex-wrap,align-content就会控制弹性子元素在副轴上的间距。如果子元素没有换行,就会忽略align-content | flex-start flex-end center stretch space-between space-around |
5.4.2 理解弹性子元素的属性
前面已经介绍了弹性子元素的flex-grow、flex-shrink、flex-basis以及它们的简写属性flex(参见5.2节)。接下来再介绍两个弹性子元素的属性:align-self和order。
- align-self属性
该属性控制弹性子元素沿着容器副轴方向的对齐方式。它跟弹性容器的align-items属性效果相同,但是它能单独给弹性子元素设定不同的对齐方式。auto为初始值,会以容器的align-items值为
准。其他值会覆盖容器的设置。align-self属性支持的关键字与align-items一样:flex-start、flex-end、center、stretch以及baseline。
- align-self属性
- order属性
正常情况下,弹性子元素按照在HTML源码中出现的顺序排列。它们沿着主轴方向,从主轴的起点开始排列。使用order属性能改变子元素排列的顺序。还可以将其指定为任意正负整数。如果多个弹
性子元素有一样的值,它们就会按照源码顺序出现。初始状态下,所有的弹性子元素的order都为0。指定一个元素的值为-1,它会移动到列表的最前面;指定为1,则会移动到最后。可以按照需要给每个子元素指定order以便重新编排它们。这些值不一定要连续。
- order属性
5.4.3 使用对齐属性
用span而不是div来放置文字,因为span默认就是行内元素。如果因为某些原因CSS加载失败,或者浏览器不支持Flexbox,那么$20.00仍然会在一行显示。下面的代码清单里,使用justify-content让弹性子元素在弹性容器里水平居中,然后用align-items和align-self控制文字的垂直对齐。将代码清单5-11添加到样式表。
.
centered {
text-align: center;
}
.
cost {
display: flex;
justify-content: center; (以下2行)让弹性子元素在主轴和副轴方向上均居中
align-items: center;
line-height: .7;
}
.cost > span {
margin-top: 0; ←----覆盖猫头鹰选择器设置的外边距
}
.cost-currency {
font-size: 2rem; (以下7行)给价格的各个部分设置不同的字号
}
.cost-dollars {
font-size: 4rem;
}
.cost-cents {
font-size: 1.5rem;
align-self: flex-start; ←---- 覆盖这个子元素的align-items,将其与容器顶部而不是中间对
}
.cta-button {
display: block;
background-color: #cc6b5a;
color: white;
padding: .5em 1em;
text-decoration: none;
}
5.5 值得注意的地方
并不是所有浏览器都完美地实现了Flexbox,尤其是IE10和IE11。Flexbox在大多数情况下可以正常工作,但是可能会在一些环境下遇到bug。一定要确保在你想要支持的旧版浏览器上充分测试它。
与其花费时间讨论你可能或者永远不会遇到的bug,我更愿意推荐一个特别棒的资源,叫Flexbugs。它的GitHub页面维护了所有已知的Flexbox的浏览器bug(本书写作时总共有14个),解释了哪些环境下会导致这些bug,并大部分情况下给出了解决方案。如果你发现在某个浏览器下Flexbox布局表现得不太一样,请访问这个页面看看是不是遇到了其中的浏览器bug。
6 网格布局
就像是坐标系或者是写字用的田字格,可以设计和做出很复杂的页面
6.1 网页布局开始新纪元
构建基础网格
首先我们定义一个容器,然后设置元素display:grid 这样该元素变成了某种容器,表现的像个块级元素,100%填充可用宽度,你也可以使用inline-grid,虽然书中没写
.grid {
display: grid; ←---- 将元素设为网格容器
grid-template-columns: 1fr 1fr 1fr; ←---- 定义等宽的三列
grid-template-rows: 1fr 1fr; ←---- 定义等高的两行
grid-gap: 0.5em;
}
.grid > * {
background-color: darkgray;
color: white;
padding: 2em;
border-radius: 0.5em;
}
rid-template-columns是渲染三列,然后长度为1fr是分数单位,三列就是1fr等分,grid-template-rows定义等高的三列,不一定非得用分数单位,可以使用其他的单位,比如px、em或百分数。也可以混搭这几种单位,例如,grid-template-columns:300px 1fr定义了一个固定宽度为300px的列,后面跟着一个会填满剩余可用空间的列。2fr的列宽是1fr的两倍,grid-gap属性定义了每个网格单元之间的间距。也可以用两个值分别指定垂直和水平方向的间距(比如grid-gap: 0.5em 1em)。
6.2 网格刨析
- 网格线(grid line)——网格线构成了网格的框架。一条网格线可以水平或垂直,也可以位于一行或一列的任意一侧。如果指定了grid-gap的话,它就位于网格线上。
- 网格轨道(grid track)——一个网格轨道是两条相邻网格线之间的 空间。网格有水平轨道(行)和垂直轨道(列)。
- 网格单元(grid cell)——网格上的单个空间,水平和垂直的网格 轨道交叉重叠的部分。
- 网格区域 (grid area)——网格上的矩形区域,由一个到多个网格单元组成。该区域位于两条垂直网格线和两条水平网格线之间
repeat()函数是重复设置的列宽,例如repeat(3,2fr,1fr)
6.2.1 网格线的编号
网格编号在主轴上是从左向右递增,负轴是自上而下递增grid-column: 1 / 3 是从编号1的网格线跨域到网格线3,下面的同理grid-row: 3 / 5
说明 这些属性实际上是简写属性:grid-column是grid-column-start和grid-column-end的简写;grid-row是grid-row-start和grid-row-end的简写。中间的斜线只在简写属性里用于区分两个值,斜线前后的空格不作要求。
6.2.2 与flexbox配合
网格布局和flex布局是互补关系,不是互相独立的关系。
- flex本质上是一维,而网格是二维的
- flex是以内容为切入点,网格布局是以整体为布局
当设计要求元素在两个维度上都对齐时,使用网格。当只关心一维的元素排列时,使用Flexbox。在实践中,这通常(并非总是)意味着网格更适合用于整体的网页布局,而Flexbox更适合对网格区域内的特定元素布局。继续用网格和Flexbox,你就会对不同情况下该用哪种布局方式得心应手。
6.3 替代语法
6.3.1 命名的网格线
常规声明方式
grid-template-columns: [start] 2fr [center] 1fr [end];下面方式没有
grid-column: start / center;repeat()里声明了一条命名的水平网格线,于是每条水平网格线被命名为row(除了最后一条)。这看起来很不可思议,但是重复使用同一个名称完全合法。将网格元素放在第二个“col”网格线处,跨越两个轨道
(col 2 /span 2)多命名声明方式
grid-template-columns: [left-start] 2fr
[left-end right-start] 1fr
[right-end];
grid-template-rows: repeat(4, [row] auto);
6.3.2 命名网格区域
不知可以命名网格线,我们还可以命名网格区域,grid-template属性和网格元素的grid-area属性。grid-template-areas属性使用了一种ASCII art的语法,可以直接在CSS中画一个可视化的网格形象。该声明给出了一系列加引号字符串,每一个字符串代表网格的一行,字符串内用空格区分每一列。
代码清单6-7
6.4 显式和隐式网格
使用grid-template-* 属性定义网格轨道时, 创建的是显式网格
代码清单6-9
访问Grid by Example网站的文章auto-fill vs. auto-fit可以看到两者区别的示例。
6.4.1 添加变化
我们想1X1的图片变成2X2的网格区域。
grid-auto-flow它可以控制布局算法的行为。 它的初始值是row, 上一段描述的就是这个值的行为。如果值为column, 它就会将元素优先放在网格列中, 只有当一列填满了, 才会移动到下一行。
grid-auto-flow: column dense
子网格
网格有一个限制是要求用特定的DOM结构, 也就是说, 所有的网格元素必须是网格容器的直接子节点。 因此, 不能将深层嵌套的元素在网格上对齐
6.4.2 让网格元素填满网格轨道
object-fit控制图片在盒子内渲染的方式
代码清单6-11
6.5 特性查询
6.6 对齐
justify-contentjustifyitemsjustify-self
start——将网格轨道放到网格容器的上/左(Flexbox里则是flexstart)
end——将网格轨道放在网格容器的下/右(Flexbox里则是flexend)
center——将网格轨道放在网格容器的中间。
stretch——将网格轨道拉伸至填满网格容器。
space-between——将剩余空间平均分配到每个网格轨道之间(它
能覆盖任何grid-gap值。
space-around——将空间分配到每个网格轨道之间, 且在两端各
加上一半的间距。
space-evenly——将空间分配到每个网格轨道之间, 且在两端各
加上同等大小的间距(Flexbox规范不支持。
7 定位和层叠上下文
7.1 固定定位
固定定位让元素相对视口定位.元素position: fixed就能将元素放在视口的任意位置,是相对于视口为坐标系然后进行定位,用: top,right,bottom,left 这些属性的值决定了固定定位的元素与浏览器视口边缘的距离
7.1.1 用固定定位创建一个模态框
display属性的用法与区别
{display: none;}
{display: inline;}
{display: block;}
{display: inline-block;}
7.1.2 控制定位元素的大小
我们通过下面的属性控制元素的大小
position: fixed;
top: 1em;
right: 1em;
width: 20% /*width是视口的20%*/
/* right-margin: 20%。 是外边距是视口的20% */
7.2 绝对定位
绝对定位的行为是相对最近的祖先定位元素跟固定元素一样, 属性top、 right、 bottom和left决定了元素的边缘在包含块里的位置。
这里边最近的祖先元素是坐标轴
7.2.1 让Close按钮绝对定位
代码清单7-3
因为.modal-body是.modal-close的直接父元素,这时候你调节.modal-close会发现那个close按钮会相对直接父元素进行定位
说明 如果祖先元素都没有定位, 那么绝对定位的元素会基于初始包含块(initial containing block) 来定位。 初始包含块跟视口一样大, 固定在网页的顶部。
7.2.2 定位伪元素
- 在使用CSS之前, HTML本身必须有意义
text-indent属性将文字推到右边, 溢出元素
7.3 相对定位
给元素加上相对定位也不会影响旁边的元素
说明 跟固定或者绝对定位不一样, 不能用top、 right、 bottom
和left改变相对定位元素的大小。 这些值只能让元素在上、 下、
左、 右方向移动。 可以用top或者bottom, 但它们不能一起用
(bottom会被忽略) 。 同理, 可以用left或right, 但它们也不
能一起用(right会被忽略) 。
7.3.1 创建一个下拉菜单
下拉菜单容器包含两个子元素: 一个始终显示的灰色矩形标签以及一个下拉菜单。 下拉菜单用显示和隐藏表示菜单展开和收起。 因为它会是绝对定位的, 所以当下拉菜单显示时不会改变网页的布局, 这意味着它显示时会出现在其他内容前面。
7.3.2 创建一个css三角形
7.4 层叠上下文和z-index
z-index值越大在Z轴上就越靠上,也就是离屏幕观察者越近。最后才发现这个认识存在很大的问题
7.4.1 理解渲染过程和层叠顺序
浏览器将HTML解析为DOM的同时还创建了另一个树形结构,叫作渲染树(render tree)
定位元素时, 这种行为会改变。 浏览器会先绘制所有非定位的元素, 然后绘制定位元素。 默认情况下, 所有的定位元素会出现在非定位元素前面.
我们使用相对定位之后,会改变元素的渲染顺序
7.4.2 用z-index控制层叠顺序
z-index属性的值可以是任意整数(正负都行) 。 表示的是笛卡儿x-y-z坐标系里的深度方向。拥有较高z-index的元素出现在拥有较低z-index的元素前面。z-index的行为很好理解, 但是使用它时要注意两个小陷阱。 第一, zindex只在定位元素上生效, 不能用它控制静态元素。 第二, 给一个定位元素加上z-index可以创建层叠上下文。
7.4.3 理解层叠上下文
什么是层叠上下文?其实就是判断元素在Z轴上的堆叠顺序,不仅仅是直接比较两个元素的z-index值的大小,这个堆叠顺序实际由元素的层叠上下文、层叠等级共同决定。
- 什么是“层叠等级”
- 普通元素的层叠等级优先由其所在的层叠上下文决定。
- 层叠等级的比较只有在当前层叠上下文元素中才有意义。不同层叠上下文中比较层叠等级是没有意义的。
- 如何产生“层叠上下文”
- HTML中的根元素< html >< /html >本身就具有层叠上下文,称为“根层叠上下文”。
- 普通元素设置position属性为非static值并设置z-index属性为具体数值,产生层叠上下文。
- CSS3中的新属性也可以产生层叠上下文。
- 什么是“层叠顺序”
- 左上角”层叠上下文background/border”指的是层叠上下文元素的背景和边框。
- inline/inline-block元素的层叠顺序要高于block(块级)/float(浮动)元素。
- 单纯考虑层叠顺序,z-index: auto和z-index: 0在同一层级,但这两个属性值本身是有根本区别的。
7.5 粘性定位
流盒:粘性定位中有一个“流盒”(flow box)的概念,指的是粘性定位元素最近的可滚动元素(overflow 属性值不是 visible 的元素)的尺寸盒子,如果没有可滚动元素,则表示浏览器视窗盒子。
8 响应式设计
什么是响应式?就是使用一套前端代码能在不同的客户端上有不同的渲染效果。
响应式设计三大原则:
- 移动平台优先设计
- @media规则,也叫媒体查询,样式只会在特定条件下生效
- 流式布局,允许根据视口宽度缩放尺寸
8.1 移动优先
一旦我们把移动端做好了,我们就可以“渐进式”的为更大屏幕设计。
断点:是一个视口的临界值,超过或者小于这个视口临界值css样式就会改变。也就是我们根据视口的大小来决定哪些样式生效。
考虑大视口:当我们完成移动端初步的构想之后,我们就要考虑大视口。
代码示例清单8-2
8.1.1 创建移动版的菜单
提示 当设计移动触屏设备的时候, 确保所有的关键动作元素都足够大, 能够用一个手指轻松点击。 千万不要让用户放大页面, 才能点中一个小小的按钮或者链接。
8.1.2 给视口添加meta标签
meta标签: 这个HTML标签告诉移动设备, 你已经特意将网页适配了小屏设备。 如果不加这个标签, 移动浏览器会假定网页不是响应式的, 并且会尝试模拟桌面浏览器
meta标签的content属性里包含两个选项
- 首先, 它告诉浏览器当解析CSS时将设备的宽度作为假定宽度, 而不是一个全屏的桌面浏览器的宽度
- 其次当页面加载时, 它使用initial-scale将缩放比设置为100%
其余meta标签属性,此处要具体查谷歌user-scalable=no阻止用户在移动设备上用两个手指缩放
代码清单8-6
8.2 媒体查询
其实原理很简单,就是我们设计一个”断点”,大于这个”断点”就是另外一套样式。
.title > h1 {
color: #333;
text-transform: uppercase;
font-size: 1.5rem;
margin: .2em 0;
}
@media (min-width: 35em) {
.title > h1 {
font-size: 2.25rem;
}
}
当视口小于35em时候现在根据视口大小, 网页标题有两种不同的字号。 当视口小于35em的时候是1.5rem, 大于35em的时候是2.25rem。
560px这个临界值被称为断点。 大多数情况下, 整个样式表里的媒体查询只会复用少数几个断点
8.2.1 媒体查询的类型
- 联合媒体查询
@media (min-width: 20em) and (max-width: 35em) { ... }min-width匹配视口大于特定宽度的设备,max-width匹配视口小于特定宽度的设备。 它们被统称为媒体特征(media feature)
(min-height: 20em)——匹配高度大于等于20em的视口。(max-height: 20em)——匹配高度小于等于20em的视口。(orientation: landscape)——匹配宽度大于高度的视口。(orientation: portrait)——匹配高度大于宽度的视口。(min-resolution: 2dppx)——匹配屏幕分辨率大于等于2dppx(dppx指每个CSS像素里包含的物理像素点数) 的设备, 比如视网膜屏幕。(max-resolution: 2dppx)——匹配屏幕分辨率小于等于2dppx的设备。
基于分辨率的媒体查询比较棘手, 因为该特征比较新, 浏览器支持得不太好。单位是dppx,因此需要使用dpi单位代替。因此使用带有前缀的媒体特征
媒体查询放在标签里,比如<linkrel="stylesheet" media="(min-width: 45em)"href="large-screen.css" />
- 媒体类型
媒体类型(media type) 。 常见的两种媒体类型是screen和print
@media print {
* {
color: black !important;
background: none !important;
}
}
8.2.2 给网页添加断点
总是确保每个媒体查询都位于它要覆盖的样式之后, 这样媒体查询内的样式就会有更高的优先级
.title { ←---- 移动端样式, 对所有的断点都生效
}
@media (min-width: 35em) { ←---- 中等屏幕的断点: 覆盖对应的移动端样式
.title {
}
}
@media (min-width: 50em) { ←---- 大屏幕断点: 覆盖对应的小屏幕和中等屏幕断点的样式
.title {
}
}
8.2.3 添加响应式的列
@media (min-width: 35em) {
.row {
display: flex;
margin-left: -.75em;←---- 使用负的外边距将行容器扩大, 补偿列的外边距(参见第4章,
margin-right: -.75em;
}
.column {
flex: 1;
margin-right: 0.75em; ←---- 添加列间距
margin-left: 0.75em;
}
}
8.3 流式布局
流式布局中, 主页面容器通常不会有明确宽度, 也不会给百分比宽度, 但可能会设置左右内边距, 或者设置左右外边距为auto, 让其与视口边缘之间产生留白。 也就是说容器可能比视口略窄, 但永远不会比视口宽
任何列都用百分比来定义宽度(比如, 主列宽70%, 侧边栏宽30%) 。 这样无论屏幕宽度是多少都能放得下主容器。
让元素能够始终填满屏幕。 要习惯将容器宽度设置为百分比, 而不是任何固定的值
8.3.1 给大视口添加样式
代码清单8-12 在大屏的断点处增加内边距
8.3.2 处理表格
代码清单8-14 在大屏的断点处增加内边距
8.4 响应式图片
8.4.1 不同视口大小使用不同的图片
说白了就是在不同媒体查询中指定不同的图片
8.4.2 使用srcset提供对用的图片
这个属性是HTML的一个较新的特性。 它可以为一个标签指定不同的图片URL, 并指定相应的分辨率。 浏览器会根据自身需要决定加载哪一个图片(如代码清单8-16所示)
代码清单8-16 在大屏的断点处增加内边距
9 模块化css库
模块化CSS(Modular CSS) 是指把页面分割成不同的组成部分, 这些组成部分可以在多种上下文中重复使用, 并且互相之间没有依赖关系。最终目的是, 当我们修改其中一部分CSS时, 不会对其他部分产生意料之外的影响.远离就像是组合家具
9.1 基础样式: 打好基础
每个样式表的开头都要写一些给整个页面使用的通用规则, 模块化CSS也不例外
normalize.css的库, 这个小样式表可以协助消除不同的客户端浏览器渲染上的不一致。 可以从
选择器不应该使用类名或者ID来匹配元素, 应只用标签类型或者偶尔用用伪类选择器。 核心思想是这些基础样式提供了一些默认的渲染
基础样式配置完成以后, 很少会再修改。 我们会在基础样式的稳定表现之上, 构建模块化CSS。 在样式表中, 基础样式后面的内容将主要由各种模块组成
9.2 一个简单的模块
解决办法就是把按钮重构成一个可复用的模块, 不受页面位置的限制。创建模块不但可以精简代码(减少重复) , 还可以保证视觉一致性。 这样看上去更专业, 不会给人仓促堆砌的感觉。 用户在潜意识里也会更容易相信我们的应用程序
9.2.1 模块的变体
通过定义一个以模块名称开头的新类名来创建一个修饰符,例如, 消息模块的error修饰符应该叫作message-error。 通过包含模块名称, 可以清楚地表明这个类属于消息模块。
搞不懂代码就意味着bug变得常见
- 按钮模块的变体
说白就是就是同一类型的组件,不同的颜色
- 不要使用依赖语境的选择器
如果没有模块化CSS, 我们可能会使用类似于.page-header .dropdown的选择器, 先选中要修改的下拉菜单, 然后通过选择器写一些样式, 覆盖dropdown类提供的默认颜色。 现在要写模块化CSS, 这样的选择器是严格禁用的。
- 第一, 我们必须考虑把这段代码放在哪里, 是和网页头部的样式放在一起, 还是跟下拉菜单的样式放在一起?
- 第二, 这种做法提升了选择器优先级。 当下次需要修改代码的时候, 我们需要满足或者继续提升优先级
- 第三, 后面我们可能需要在其他场景用到深色的下拉列表。 刚才创建的这个下拉列表是限定在网页头部使用的
- 第四, 重复使用这种写法会产生越来越长的选择器, 将CSS跟特定的HTML结构绑定在一起。 例如, 如果有个#products-page.sidebar .social-media div:first-child h3这样的选择器, 样式集就会和指定页面的指定位置紧紧耦合
9.2.2 多元素模块
每个模块应该只做一件事情。 消息模块的职责是使消息提示醒目; 媒体模块的职责是在一段文本中配置一张图片。 我们可以简洁明了地概括出它们的目标。 有的模块是为了版面布局, 有的是为了编写体例
一个组件一定是由多个元素构建
9.3 把模块组合成更大的结构
9.3.1 拆分不同模块的职责
第二个模块叫作菜单, 是放置链接的列表。 把菜单模块的一个实例放入下拉模块的容器内, 就可以构成完整的界面了。
- 在模块里使用定位
- 状态类
- is-open类在下拉模块中有特定的用途。 我们在模块里使用JavaScript动态地添加或移除它。 它也是状态类(state class) 的一个示例, 因为它代表着模块在当前状态下的表现。
- 预处理器和模块化CSS
- 菜单模块
9.3.2 模块命名
模块的命名应该有意义, 无论使用场景是什么。 同时也要避免使用简单地描述视觉效果的名称。
比较好的模块名称包括面板(panel) 、 警告(alert) 、 可折叠的部分(collapsible-section) 、 表单控制项(form-control) 等。 如果你从一开始就对网站的整体设计有全面的了解, 会有助于命名。
为模块的变体类命名的时候, 应该遵守同样的原则。 例如, 如果已经有按钮模块了, 就不应该使用button–red和button–blue命名红色和蓝色变体子类。 网站设计在将来有可能会改变, 你不知道这些按钮的颜色会不会也跟着变化。 应该使用一些更有意义的名称, 比如button–danger和button–success。
9.4 工具类
从某种意义上讲, 工具类有点像小号的模块。 工具类应该专注于某种功能, 一般只声明一次。 我通常把这些工具类放在样式表的底部, 模块代码的下面。
工具类的作用立竿见影。 在页面上做点小事儿的时候不需要创建一个完整的模块, 这种情况下可以用一个工具类来实现。 但是不要滥用工具类。 对于大部分网站, 最多十几个工具类就够用了。
常用工具类有哪些?如果书写自己工具类
9.5 CSS方法论
- OOCSS——面向对象的CSS, 由Nicole Sullivan创建。
- SMACSS——可扩展的、 模块化CSS架构, 由Jonathan Snook创建。
- BEM——块(Block) 、 元素(Element) 和修饰符(Modifier) ,由Yandex公司提出。
- ITCSS——倒三角形CSS, 由Harry Roberts创建。
10 模式库
- 创建模式库, 收录模块
- 开发过程中引入模式库
- 使用CSS优先的方案书写样式
- 安全地编辑和删除CSS
- 使用Bootstrap之类的CSS框架
10.1 KSS简介
虽然创建模式库的时候不使用任何工具也可以, 但有了工具的帮助会容易很多。 有不少相关功能的工具库可以使用, 在搜索引擎里搜索“styleguide generator”, 就可以找到大量结果。 无法确定这些工具里最好的是哪个, 但是KSS确实是其中的佼佼者。 KSS是Knyle Style Sheets的简写(“Knyle” 来源于作者的名字Kyle Neath)
10.1.1 配置KSS
- 初始化项目
npm init -y - 安装依赖
npm install --save-dev kss - 添加KSS配置
- 在项目目录下新建一个名为kss-config.json的文件
{ "title": "My pattern library", "source": [ ←---- CSS源文件的目录路径( KSS将要扫描的) "./css" ], "destination": "docs/", ←---- 生成的模式库文件将写入的路径 "css": [ "../css/styles.css" ←---- 样式表文件路径( 相对于destination目录) ], "js": [ "../js/docs.js" ←---- 一些JavaScript文件路径( 相对于destination目录) ] }, "scripts": { "build": "kss --config kss-config.json", ←---- 定义构建命令 "test": "echo \"Error: no test specified\" && exit 1" }, - css和js字段里列出的每个文件都会被添加到模式库页面。 我们已经为它们各自配置了一个css和js目录, 现在就可以去创建这两个目录和里面的源文件( css/styles.css和js/docs.js) 。 文件目前是空的,很快就会向里面添加内容
- 在项目目录下新建一个名为kss-config.json的文件
10.1.2 编写KSS文档
10.2 改变编写CSS的方式
10.2.1 CSS优先的工作流程
- 页面开发时, 先有一个草图或者原型图或者其他可以展示页面的设计方式
- 看看模式库。 找找现有模块, 如果有满足页面需求的模块就直接使用。 然后从页面的外层(主页面布局和容器) 开始, 按自己熟悉的方式编写CSS。 如果使用现有模块可以构建整个页面, 就不需要写新的CSS
- 你会发现有时候需要用到一些模式库提供不了的功能。 项目开发早期这种情况很常见, 到后面就会少很多。 这时候就需要开发一个或几个新模块, 或者现有模块的新变体
10.2.2 像API一样使用模式库
这节还需要再看看
11 背景、 阴影和混合模式
- 线性渐变和径向渐变
- 盒阴影和文字阴影
- 调整背景图片的大小和位置
- 使用混合模式, 让背景和内容相结合
11.1 渐变
- background-image——指定一个文件或者生成的颜色渐变作为背景图片。
- background-position——设置背景图片的初始位置。
- background-size——指定元素内背景图片的渲染尺寸。
- background-repeat——决定在需要填充整个元素时, 是否平铺图片。
- background-origin——决定背景相对于元素的边框盒、 内边距框盒(初始值) 或内容盒子来定位。
- background-clip——指定背景是否应该填充边框盒(初始值) 、 内边距框盒或内容盒子。
- background-attachment——指定背景图片是随着元素上下滚动(初始值) , 还是固定在视口区域。 注意, 使用fixed值会对页面性能产生负面影响。
- background-color——指定纯色背景, 渲染到背景图片下方。
11.1.1 使用多个颜色点
11.1.2 使用径向渐变
11.2 阴影
11.2.1 使用渐变喝阴影形成立体感
11.2.2 使用扁平化设计创造元素
11.2.3 让按钮看起来时尚
11.3 混合模式
11.3.1 为图片着色
11.3.2 理解混合模式的类型
11.3.3 为图片添加纹理
11.3.4 使用混合模式
12 对比,颜色和间距
12.1 对比最重要
12.1.1 建立模式
这部分的工作更像是设计师的工作
12.1.2 还原设计稿
BEM风格来为类命名, 以便清楚地知道哪个元素属于哪个模块。 双下划线代表模块的子元素, 比如hero__inner; 双连字符代表模块变体, 比如button–cta
12.2 颜色
因为这些颜色会在CSS中多次重复出现, 所以将它们指定为变量可以节省很多时间。 另外, 如果总是一次次地输入十六进制值, 无法保证一定不出错
我们先为页面统一添加一些基础样式, 也包括为调色板中的每种颜色指定变量
12.2.1 理解颜色表示法
rgb()函数是一种描述红、 绿、 蓝彩色值的颜色表示法, 使用十进制而非十六进制。 它使用0-255取代了00-FF, 比如rgb(0, 0, 0)代表纯黑色(相当于#000) , rgb(136, 0, 0)代表砖红色(相当于#800)。
hsl()函数需要3个参数。 第一个参数表示色相, 是一个0~359的整数值。 这代表色相环上的360度, 从红色(0) 、 黄色(60) 、 绿色(120) 、 青色(180) 、 蓝色(240) 、 洋红色(300) 依次过渡, 最后回到红色。 第二个参数表示饱和度, 是一个代表色彩强度的百分数, 100%的时候颜色最鲜艳, 0%就意味着没有彩色, 只是一片灰色。第三个参数表示明度, 也是百分数, 代表颜色有多亮(或者多暗) 。 大部分鲜艳的颜色是使用50%的明度值。 明度值设置得越高, 颜色越浅, 100%就是纯白色; 设置得越低, 颜色越暗, 0%就是黑色。 例如, hsl(198, 73%, 46%)这个颜色值, 包含了青蓝色的色相、 偏高的饱和度(73%) 和接近50%的明度, 因此会生成一个比天蓝色稍深一些的蓝色
HSL Color Picker这个网站提供了一个交互式颜色选择器
12.2.2 添加新颜色到调色板
为某种颜色寻找一个搭配的颜色, 最简单的方式是找到它的补色(complement) 。 补色位于色相环的对侧位置, 蓝色的补色是黄色; 绿
色的补色是洋红色(或者紫色) ; 红色的补色是青色。
使用HSL颜色值时, 计算补色非常简单, 为色相值加上或者减去180即可。 核心颜色品牌绿的色相值是162, 加上180得到342的新色相值, 这是个红色, 带一点点洋红。 我们也可以通过减去180来寻找补色, 得到了-18的色相值。 色相-18其实等同于色相342, 因此hsl(-18, 87%,21%)和hsl(342, 87%, 21%)会渲染成同样的颜色。 不过建议把色相值保持在0~360的范围内, 因为这个范围内的颜色与色相对应关系我们比较熟悉。
如果你想更深入地研究颜色选择, 可以上网浏览颜色理论相关的文章。你可以从Natalya Shelburne所写的这篇著名的文章Practical Color Theory for People Who Code开始。
12.2.3 思考字体颜色的对比效果
12.3 间距
12.3.1 使用em还是px
使用像素, 短期内工作会比较轻松, 但这也意味着后面的设计将缺少弹性。可能会导致将来有更多的工作, 当然这也不一定。 如果决定使用相对单位, 前期就需要多做一些工作, 但是设计会更强大稳固。