# 基础

# 术语

  • 声明:CSS中的一行,由一个属性和一个值组成。例如 color:black。
  • 声明块:包含在大括号内的一组声明。
  • 规则集:选择器 + 声明块
  • @规则:用“@”符号开头的语法。比如@import规则或者@media查询。
  • 层叠值:作为层叠结果,应用到一个元素上的特定属性的值。
  • 计算值:浏览器根据相对单位的值计算出的绝对值。
  • 声明值:声明中的值
  • 视口:浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。
  • *:通用选择器,选中页面上所有元素
  • 普通文档流:指网页元素的默认布局行为。行内元素跟随文字的方向从左到右排列,当到达容器边缘时会换行。块级元素会占据完整的一行,前后都有换行。

# 选择器

基础选择器

  • tagname 标签选择器。优先级001
  • .class 类选择器。优先级010
  • #id ID选择器。优先级100
  • * 通用选择器。优先级000

组合选择器

  • a>b 子组合器。a的直接后代b
  • a+b 相邻兄弟组合器。紧跟在a后面的兄弟元素b。
  • a~b 通用兄弟组合器。所有在a后面的兄弟元素b。
  • a b 选择a的后代中的任何匹配b选择器的元素。
  • ab 选择所有既符合a,又符合b的元素。复合选择器。多个基础选择器可以连起来(不使用空格或者其他组合器)组成一个复合选择器。复合选择器选中的元素将匹配其全部基础选择器。例如.dropdown.is-active只能匹配同时有这两个类的元素。

伪类选择器
用于选中处于某个特定状态的元素。这种状态可能是由于用户交互,也可能是由于元素相对于其父级或兄弟元素的位置。伪类选择器始终以一个冒号(:)开始。优先级等于一个类选择器(0,1,0)。

  • :first-child——匹配的元素是其父元素的第一个子元素。
  • :last-child——匹配的元素是其父元素的最后一个子元素。
  • :only-child——匹配的元素是其父元素的唯一一个子元素(没有兄弟元素)。
  • :nth-child(an+b)——匹配的元素在兄弟元素中间有特定的位置。公式an+b里面的a和b是整数,n取所有自然数,包括0。
  • :nth-last-child(an+b)——类似于:nth-child(),但不是从第一个元素往后数,而是从最后一个元素往前数。
  • :first-of-type——类似于:first-child,但不是根据在全部子元素中的位置查找元素,而是根据拥有相同标签名的子元素中的数字顺序查找第一个元素。
  • :last-of-type——匹配每种类型的最后一个子元素。
  • :only-of-type——该选择器匹配的元素是满足该类型的唯一一个子元素。
  • :nth-of-type(an+b)——根据目标元素在特定类型下的数字顺序以及特定公式选择元素,类似于:nth-child。
  • :nth-last-of-type(an+b)——根据元素类型以及特定公式选择元素,从其中最后一个元素往前算,类似于:nth-last-child。
  • :not(<selector>)——匹配的元素不匹配括号内的选择器。括号内的选择器必须是基础选择器,它只能指定元素本身,无法用于排除祖先元素,同时不允许包含另一个排除选择器。
  • :empty——匹配的元素必须没有子元素。注意,如果元素包含空格就无法由该选择器匹配,因为空格在DOM中属于文本节点。
  • :focus——匹配通过鼠标点击、触摸屏幕或者按Tab键导航而获得焦点的元素。
  • :hover——匹配鼠标指针正悬停在其上方的元素。
  • :root——匹配文档根元素。

伪元素选择器

  • ::before——创建一个伪元素,使其成为匹配元素的第一个子元素。该元素默认是行内元素,可用于插入文字、图片或其他形状。必须指定content属性才能让元素出现,例如:.menu::before。
  • ::after——创建一个伪元素,使其成为匹配元素的最后一个子元素。类似::before。
  • ::first-letter——用于指定匹配元素的第一个文本字符的样式,例如:h2::first-letter。
  • ::first-line——用于指定匹配元素的第一行文本的样式。
  • ::selection——用于指定用户使用鼠标高亮选择的任意文本的样式。通常用于改变选中文本的background-color。只有少数属性可以使用,包括color、background-color、cursor、text-decoration。

属性选择器
用于根据HTML属性匹配元素。其优先级与一个类选择器(0,1,0)相等。

  • [attr]——匹配的元素拥有指定属性attr,无论属性值是什么
  • [attr="value"]——匹配的元素拥有指定属性attr,且属性值等于指定的字符串值
  • [attr^="value"]——“开头”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值的开头是指定的字符串值,例如:a[href^="https"]
  • [attr$="value"]——“结尾”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值的结尾是指定的字符串值,例如:a[href$= ".pdf"]
  • [attr*="value"]——“包含”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值包含指定的字符串值,例如:[class*="sprite-"]
  • [attr~="value"]——“空格分隔的列表”属性选择器。该选择器匹配的元素拥有指定属性attr,且属性值是一个空格分隔的值列表,列表中的某个值等于指定的字符串值
  • [attr|="value"]——匹配的元素拥有指定属性attr,且属性值要么等于指定的字符串值,要么以该字符串开头且紧跟着一个连字符。适用于语言属性,因为该属性有时候会指定一种语言的子集(比如墨西哥西班牙语,es-MX,或者普通的西班牙语,es),例如:[lang|="es"]

# 浏览器的开发者工具

# 样式

  • element.style 为行内样式
  • 靠近顶部的样式会覆盖下面的样式。
  • 被覆盖的样式上划了删除线。
  • 右侧显示了每个规则集的样式表和在源代码中行号。
  • 顶部的筛选框可以选择特定的声明,同时隐藏其他声明。
  • 按住shift,再点击颜色前的方框,可以改变颜色的表示法

# Element

  • 选择指定元素,右键菜单中可以设置强制元素进入:active,:hover等状态。

# 层叠

当声明冲突时,层叠会依据三种条件解决冲突。

  1. 样式表的来源:样式是从哪里来的,有三级,浏览器默认样式(也称用户代理样式),作者自定义样式(即开发者写的CSS),作者的!important样式。后面的会覆盖前面的。
  2. 选择器优先级:行内样式 > ID选择器 > 类选择器 > 标签选择器。选择器最高优先级相同时,比其数量,数量多的优先级高。
    • 伪类选择器(如:hover)和属性选择器(如[type="input"])与一个类选择器的优先级相同。通用选择器(*)和组合器(>、+、~)对优先级没有影响。
  3. 源码顺序:样式在样式表里的声明顺序。晚出现的优先级更高。

# 继承(inherit 属性值)

  • 不是所有的属性都能被继承。默认情况下,只有特定的一些属性能被继承,通常是我们希望被继承的那些。
  • 文本相关的属性: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也能被继承。

PS: initial属性,即CSS属性的初始值。可用于撤销某元素的样式。

# 简写属性

简写属性是用于同时给多个属性赋值的属性。例如

  • font:它指定了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是上、右、下、左四个边框宽度的简写属性。

大多数简写属性可以省略一些值,只指定我们关注的值。但被省略的值会被隐式地设置为初始值。
通常元素的四个方向相关的属性声明顺序为上右下左,即顺时针方向。如果声明结束时四个属性值还剩有没指定的,则没有指定的一边会取其对边的值。指定三个值时,左边和右边都会使用第二个值。指定两个值时,上边和下边会使用第一个值,左边和右边使用第二个值。如果只指定一个值,那么四个方向都会使用这个值。

还有一些属性只支持最多指定两个值,这些属性包括background-position、box-shadow、text-shadow(虽然严格来讲它们并不是简写属性)。其声明顺序为先x,后y。即先水平方向,再垂直方向。

# 相对单位

# em & rem

  • em
    • 对于font-size属性,em是根据继承的字号来计算的,即父元素的font-size。
    • 对于非font-size属性,em是根据元素的font-size属性计算的。
    • em的复杂之处就在于同时用它指定一个元素的字号和其他属性。这时,浏览器必须先计算字号,然后使用这个计算值去算出其余的属性值。这两类属性可以拥有一样的声明值,但是计算值不一样。
    • em的好处。可以定义一个元素的大小,然后只需要改变字号就能整体缩放元素。padding、height、width、border-radius等属性用em很方便。
    • 对于嵌套结构,如果都使用em单位,则字体大小会逐级放大或缩小,要避免此情况,应使用rem单位。
  • rem
    • 根据根元素的 font-size 属性计算
    • html是根元素。根节点有一个伪类选择器:root。
    • 对大多数浏览器来说,默认的字号为16px。准确地说,medium关键字的值是16px。
  • 百分数 基数为父元素的 px 值

tips:拿不准的时候,用rem设置字号,用px设置边框,用em设置其他大部分属性。

# 媒体查询

:root {
    font-size: 0.75em;
}

/* 作用于宽度 > 800px的屏幕,生效时,因源码顺序,会覆盖前面的样式 */
@media (min-width: 800px) {
    :root {
    font-size: 0.875em;
    }
}

/* 作用于宽度 > 1200px的屏幕,生效时,因源码顺序,会覆盖前面的样式 */
@media (min-width: 1200px) {
    :root {
    font-size: 1em;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 视口单位

视口——浏览器窗口里网页可见部分的边框区域。它不包括浏览器的地址栏、工具栏、状态栏。

  • vh:视口高度的1/100。
  • vw:视口宽度的1/100。
  • vmin:视口宽、高中较小的一方的1/100。
  • vmax:视口宽、高中较大的一方的1/100。

相对视口单位有一个不起眼的用途,就是设置字号,这样做的好处在于元素能够在这两种大小之间平滑地过渡,即不会在某个断点突然改变。当视口大小改变时,元素会逐渐过渡。但只使用vw或vh,字号会比较大,所以通常会结合em一起使用。
calc()函数可以对两个及其以上的值进行基本运算。当要结合不同单位的值时,calc()特别实用。它支持四则运算。加号和减号两边必须有空白,如calc(.5em + 1vh)。

# 无单位的数值

有些属性允许无单位的值(即一个不指定单位的数)。

  • 支持这种值的属性包括line-height、z-index、font-weight(700等于bold,400等于normal,等等)。
  • 任何长度单位都可以用无单位的值0,因为这些情况下单位不影响计算值,0px、0%、0em均相等。
  • 警告:一个无单位的0只能用于长度值和百分比,比如内边距、边框和宽度等,而不能用于角度值,比如度,或者时间相关的值,比如秒。

继承的怪异行为

  • 当一个元素的值定义为长度时,子元素会继承它的计算值。当使用em等单位定义行高时,它们的值是计算值,传递到了任何继承子元素上。如果子元素有不同的字号,并且继承了line-height属性,就会造成意想不到的结果,比如文字重叠。
  • 使用无单位的数值时,继承的是声明值,即在每个继承子元素上会重新算它的计算值。这样得到的结果几乎总是我们想要的。我们可以用一个无单位的数值给body设置行高,之后就不用修改了,除非有些地方想要不一样的行高。

# 自定义属性(css变量)

:root {
    --main-color: white; /* 变量声明,无实际效果 */
    --main-bg: #fff;
}
1
2
3
4
  • 变量必须在一个声明块内声明。
  • 变量名前面必须有两个连字符(--),用来跟CSS属性区分,剩下的部分可以随意命名。
  • 变量声明本身什么也没做,我们使用时才能看到效果。
  • 使用变量需使用var()函数,其第二个参数,指定了备用值。如果第一个参数指定的变量未定义,那么就会使用第二个值。
  • 注意:如果var()函数算出来的是一个非法值,对应的属性就会设置为其初始值。比如,如果在padding: var(--brand-color)中的变量算出来是一个颜色,它就是一个非法的内边距值。这种情况下,内边距会设置为0。
  • 自定义属性的声明能够层叠和继承,可以在多个选择器中定义相同的变量,则该变量在网页的不同地方有不同的值。

用JS访问和修改自定义属性。

<script type="text/javascript">
    let rootElement = document.documentElement;
    let styles = getComputedStyle(rootElement);
    let mainColor = styles.getPropertyValue('--main-bg');
    console.log(String(mainColor).trim());

    rootElement = document.documentElement;
    rootElement.style.setProperty('--main-bg', '#cdf');
</script>
1
2
3
4
5
6
7
8
9

# 盒模型

box-model 当给一个元素设置宽或高的时候,指定的是内容的宽或高,所有内边距、边框、外边距都是追加到该宽度上的。

# 元素宽度问题

在CSS中可以使用box-sizing属性调整盒模型的行为。

  • box-sizing的默认值为content-box,这意味任何指定的宽或高都只会设置内容盒子的大小。
  • box-sizing设置为border-box后,height和width属性会设置内容、内边距以及边框的大小总和。

全局修改盒模型为border-box(已是普遍做法了)

:root {
    box-sizing: border-box;
}

*,
::before,
::after {
    box-sizing: inherit; /* 盒模型通常不会被继承,但是使用inherit关键字可以强制继承。 */
}

.third-party-component {
    box-sizing: content-box; /* 如果带样式的第三方组件使用的是默认的盒模型,则恢复 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13

给列之间加上间隙

.main {
    float: left;
    width: 70%;
    background-color: #fff;
    border-radius: .5em;
}

.sidebar {
    float: left;
    width: calc(30% - 1.5em); /* 从宽度中分1.5em给外边距以形成间隙 */
    margin-left: 1.5em;
    padding: 1.5em;
    background-color: #fff;
    border-radius: .5em;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 元素高度问题

容器的高度由内容天然地决定,而不是容器自己决定。当明确设置一个元素的高度时,内容可能会溢出容器。当内容在限定区域放不下,渲染到父元素外面时,就会溢出。
用overflow属性可以控制溢出内容的行为,该属性支持以下4个值。

  • visible(默认值)——所有内容可见,即使溢出容器边缘。
  • hidden——溢出容器内边距边缘的内容被裁剪,无法看见。
  • scroll——容器出现滚动条,用户可以通过滚动查看剩余内容。在一些操作系统上,即使所有内容都可见,也会出现水平和垂直两种滚动条。不过,在这种情况下,滚动条不可滚动。
  • auto——只有内容溢出时容器才会出现滚动条。
  • 可以用overflow-x属性单独控制水平方向的溢出,或者用overflow-y控制垂直方向溢出。这些属性支持overflow的所有值,然而同时给x和y指定不同的值,往往会产生难以预料的结果。

# 等高列

任意一列的内容增加,两列的高度都会增加,同时保持底部对齐。
现代浏览器支持了CSS表格,可以轻松实现等高列,比如IE8+支持display: table,IE10+支持弹性盒子或者Flexbox,都默认支持等高列。

方案一: 用CSS表格布局替代浮动布局。给容器设置display: table,给每一列设置display:table-cell。

  1. 不像block的元素,默认情况下,显示为table的元素宽度不会扩展到100%,因此需要明确指定宽度(width:100%)。
  2. table-cell元素的外边距失效。
  3. 可以用表格元素的border-spacing属性来定义单元格的间距。该属性接受两个长度值:水平间距和垂直间距。但这会产生一个特殊的副作用,间距也会作用于表格的外边缘,导致水平方向无法对齐。
  4. 可使用负外边距解决,正的外边距会将容器的边缘往里推,而负的外边距则会将边缘往外拉。在表格容器外面包一个元素,将其左右外边距设置为−1.5em,从而抵消表格容器外侧1.5em的border-spacing。

方案二: 给容器设置display: flex,它就变成了一个弹性容器(flex container),子元素默认等高。你可以给子元素设置宽度和外边距,尽管加起来可能超过100%, Flexbox也能妥善处理。

# min-height, max-height

  • min-height指定一个最小高度。元素至少等于你指定的高度,如果内容太多,浏览器就会允许元素自己扩展高度,以免内容溢出。
  • max-heght允许元素自然地增高到一个特定界限。如果到达这个界限,元素就不再增高,内容会溢出。
  • min-width, max-width同理

# 垂直居中内容

关于vertical-align:该声明只会影响行内元素或者table-cell元素。

  • 对于行内元素,它控制着该元素跟同一行内其他元素之间的对齐关系。比如,可以用它控制一个行内的图片跟相邻的文字对齐。
  • 对于显示为table-cell的元素,vertical-align控制了内容在单元格内的对齐。如果使用CSS表格布局,则可以用vertical-align来实现垂直居中。

在容器里让内容居中最好的方式是根据特定场景考虑不同因素。做出判断前,先逐个询问自己以下几个问题,直到找到合适的解决办法。

  • 可以用一个自然高度的容器吗?给容器加上相等的上下内边距让内容居中。
  • 容器需要指定高度或者避免使用内边距吗?对容器使用display: table-cell和vertical-align: middle。
  • 可以用Flexbox吗? 如果不需要支持IE9,可以用Flexbox居中内容。
  • 容器里面的内容只有一行文字吗?设置一个大的行高,让它等于理想的容器高度。这样会让容器高度扩展到能够容纳行高。如果内容不是行内元素,可以设置为inline-block。
  • 容器和内容的高度都知道吗?将内容绝对定位。
  • 不知道内部元素的高度?用绝对定位结合变形(transform)。
  • 还不确定的话,参考howtocenterincss网站。

# 负外边距

不同于内边距和边框宽度,外边距可以设置为负值。负外边距有一些特殊用途,比如让元素重叠或者拉伸到比容器还宽。 minus-margin 负外边距的具体行为取决于设置在元素的哪边,如图所示。如果设置左边或顶部的负外边距,元素就会相应地向左或向上移动,导致元素与它前面的元素重叠,如果设置右边或者底部的负外边距,并不会移动元素,而是将它后面的元素拉过来。给元素底部加上负外边距并不等同于给它下面的元素顶部加上负外边距。

# 外边距折叠

当顶部和/或底部的外边距相邻时,就会重叠,产生单个外边距。这种现象被称作折叠。折叠外边距的大小等于相邻外边距中的最大值。即使两个元素不是相邻的兄弟节点也会产生外边距折叠。在没有其他CSS的影响下,所有相邻的顶部和底部外边距都会折叠。
PS:只有上下外边距会产生折叠,左右外边距不会折叠。折叠外边距就像“个人空间”。如果在公交车站站着两个人,他们每个人都认为较为舒适的个人空间应为3英尺,那么他们就会乐意间隔3英尺,而不必间隔6英尺才让双方满意。

# 容器内堆叠元素的间距

容器的内边距和内容的外边距之间的相互作用处理起来很棘手。

例如:容器有1.5em的padding,想将容器内的多个块级元素隔开一个间隙,使用margin-top:.5em。元素之间确实隔开了,但容器顶部1.5em的padding加上第一个元素的.5em margin会导致容器顶部空间过大。

解决方法为:使用兄弟选择器。.button-link+.button-link,这不会选择第一个子元素。
更通用的方法为:猫头鹰选择器。*+*。这会选中页面上有着相同父级的非第一个子元素。

# 渲染

浏览器计算好了页面上哪些样式应用于哪些元素上之后,需要把这些样式转化成屏幕上的像素,这个过程叫作渲染(rending)。渲染可以分为三个阶段:布局、绘制和合成。

# 布局

在第一个阶段布局中,浏览器需要计算每个元素将在屏幕上占多大空间。因为文档流的工作方式,所以一个元素的大小和位置可以影响页面上无数其他元素的大小和位置。

任何时候改变一个元素的宽度或高度,或者调整位置属性(比如top或者left),元素的布局都会重新计算。如果使用JavaScript在DOM中插入或者移除元素,也会重新计算布局。一旦布局发生改变,浏览器就必须 重排(reflow) 页面,重新计算所有其他被移动或者缩放的元素的布局。

# 绘制

布局之后是绘制。这个过程就是填充像素:描绘文本,着色图片、边框和阴影。这不会真正显示在屏幕上,而是在内存中绘制。页面各部分生成了很多的图层(layers)

如果改变某个元素的背景颜色,就必须重新绘制它。但因为更改背景颜色不会影响到页面上任何元素的位置和大小,所以这种变化不需要重新计算布局。

某些条件下,页面元素会被提取到自己的图层。这时候,它会从页面的其他图层中独立出来单独绘制。浏览器把这个图层发送到计算机的GPU进行绘制,而不是像主图层那样使用主CPU绘制。这就是硬件加速(hardware acceleration)。

# 合成

在合成(composite)阶段,浏览器收集所有绘制完成的图层,并把它们提取为最终显示在屏幕上的图像。合成过程需要按照特定顺序进行,以确保图层出现重叠时,正确的图层显示在其他图层之上。

opacity和transform这两个属性如果发生改变,需要的渲染时间就会非常少。当我们修改元素的这两个属性之一时,浏览器就会把元素提升到其自己的绘制图层并使用GPU加速。因为元素存在于自己的图层,所以整个图像变化过程中主图层将不会发生变化,也无须重复的重绘。

因此,处理过渡或者动画的时候,尽量只改变transform和opacity属性。如果有需要,可以修改那些只导致重绘而不会重新布局的属性。只有在没有其他替代方案的时候,再去修改那些影响布局的属性。

# css属性建议书写顺序

显示属性 自身属性 文本属性和其他修饰
display width font
visibility height text-align
position margin text-decoration
float padding vertical-align
clear border white-space
list-style overflow color
top min-width background

从左到右。