上一篇整理了 HTML 的结构,这一篇进入 CSS ——负责”长什么样”的部分。同样,每节都附了可以直接打开的 demo。

一、三种导入方式

demo 5:CSS 导入方式

CSS 有三种写进页面的方式:

1. 外部样式 —— 单独写成 .css 文件,用 <link> 引入。实际项目里基本都用这种,因为可以被多个页面复用,也能被浏览器缓存。

1
<link rel="stylesheet" href="css/style.css">
1
2
3
4
/* css/style.css */
h3 {
color: yellow;
}

2. 内部样式 —— 写在 <head> 里的 <style> 标签中,只对当前页面生效。

1
2
3
4
5
6
7
8
9
<style>
p {
color: blue;
font-size: 25px;
}
h2 {
color: pink;
}
</style>

3. 内联样式 —— 直接写在标签的 style 属性上,只作用于这一个元素。

1
<h1 style="color: red;">这是一个应用了内联样式的一级标题</h1>

优先级到底怎么算

常见说法是”内联 > 内部 > 外部”,这个说法不准确。真实规则是两条:先比选择器权重,权重相同才比先后顺序。

权重从高到低:

写法 权重
内联 style="" 1000
id 选择器 #header 100
类/伪类/属性选择器 .highlight 10
元素选择器 h2 1
通用选择器 * 0

关键在于:内部样式和外部样式本身没有高低之分,谁在后面谁生效(这也是为什么 <link> 通常写在 <style> 前面)。动手验证一下——把 demo 里 <link> 挪到 </style> 后面,h3 的颜色就会从外部样式的黄色生效,而不是被内部样式覆盖。

内联样式因为权重是 1000,除非用 !important,否则永远赢。这也是不建议用内联样式的原因:它会让你后面想覆盖样式时非常痛苦。

二、CSS 选择器

demo 6:CSS 选择器

选择器决定”这条样式规则作用在谁身上”。常用的一共九类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 元素选择器:所有 h2 */
h2 { color: blue; }

/* 类选择器:所有 class="highlight" 的元素(可以有很多个) */
.highlight { background-color: yellow; }

/* ID 选择器:id="header" 的那一个元素(全页面唯一) */
#header { font-size: larger; color: brown; }

/* 通用选择器:所有元素 */
* { font-family: "kaiti"; font-weight: bolder; }

/* 子元素选择器:只选 .father 的直接子元素 .son */
.father > .son { color: yellowgreen; }

/* 后代选择器:.father 内部所有层级的 p,包括孙子辈 */
.father p { color: pink; font-size: large; }

/* 相邻兄弟选择器:紧跟在 h3 后面的那一个 p */
h3 + p { background-color: purple; }

/* 伪类选择器:鼠标悬停时 */
#element:hover { background-color: aqua; }

子代选择器和后代选择器差一个字,差别很大

  • .father > .son 里的 > 表示直接子元素,孙子辈不匹配;
  • .father p 中间是空格,表示所有后代,不管嵌套多深都匹配。

demo 里的结构是这样的:

1
2
3
4
5
6
<div class="father">
<p class="son">这是一个子元素选择器示例</p>
<div>
<p class="grandson">这是一个后代元素选择器示例</p>
</div>
</div>

.son 同时被两条规则命中,为什么显示的是 yellowgreen 而不是 pink?因为 .father > .son 是两个类(权重 20),而 .father p 是一个类加一个元素(权重 11),前者胜出。.grandson 只被第二条命中,所以是粉色。

其他常用伪类与伪元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 选中第一个子元素 */
li:first-child { color: red; }

/* 选中第 2 个子元素,也可以写 2n / odd / even */
li:nth-child(2) { color: blue; }

/* 元素被按下时 */
button:active { background: gray; }

/* 伪元素:在元素内部的前/后插入内容(不在 HTML 里) */
.father::after {
content: "";
display: table;
clear: both;
}

伪类和伪元素的区别看冒号个数::hover 是伪类(选中元素的某种状态),::after 是伪元素(凭空生成一块内容)。上面这个 ::after 就是经典的清除浮动写法,下一篇会详细讲。

三、常用属性与 display

demo 7:CSS 常用属性

font 复合属性

1
2
/* 顺序:font-weight  font-size  font-family */
h1 { font: bolder 50px "Kaiti"; }

复合写法更简洁,但要注意 font-size 和 font-family 必须写,且 font-size 和 font-family 的顺序不能颠倒,否则整条声明会失效。不确定的时候分开写更稳妥:

1
2
3
4
5
h1 {
font-weight: bolder;
font-size: 50px;
font-family: "Kaiti";
}

line-height 行高

1
p { line-height: 40px; }

行高决定每行文字占的高度,是控制段落疏密的主要手段。用无单位数字(比如 1.6)比用像素更好——它会按当前字号自动换算,字号变化时不用跟着改。

行内元素设置宽高无效

这是最容易困惑的一点:

1
2
3
4
5
6
7
8
9
10
.block {
background-color: aqua;
width: 200px;
height: 100px; /* 块级元素:生效 */
}
.inline {
background-color: pink;
width: 200px;
height: 100px; /* 行内元素:无效! */
}
1
2
<div class="block">这是一个块级元素</div>
<span class="inline">这是一个行内元素</span>

<span> 的宽高完全不生效,它的尺寸由内容撑开。但有个例外:<img>、<input> 这类替换元素虽然也是行内元素,宽高却是生效的——因为它们的尺寸由外部资源决定。demo 里的两张 youda.gif 就设了 100×150 并且确实生效了。

display 转换元素类型

既然行内元素不能设宽高,那就把它转成能设的:

1
2
3
4
5
6
7
8
9
/* 让 div 变成行内元素:会和其他行内元素排在同一行,但宽高失效 */
div.inline { display: inline; }

/* 让 span 变成行内块元素:既能在同一行,又能设宽高 —— 最常用 */
span.inline-block {
display: inline-block;
background-color: blueviolet;
width: 300px;
}
display 值 是否独占一行 能否设宽高
block 是 能
inline 否 不能(替换元素除外)
inline-block 否 能

inline-block 是这三者里最实用的一个:做导航菜单、按钮、卡片排列时经常用到。下一篇的 Flex 布局则是更现代的替代方案。

四、盒子模型

demo 8:CSS 盒子模型

每个元素都可以看成一个盒子,从里到外一共四层:

1
2
3
4
5
6
7
┌─────────── margin(外边距,盒子与其他盒子的距离)───────────┐
│ ┌──────── border(边框)────────────────────────────┐ │
│ │ ┌──── padding(内边距,内容与边框的距离)────┐ │ │
│ │ │ content(内容) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

border 复合属性

1
2
3
4
5
.demo {
border: 5px solid yellow; /* 宽度 样式 颜色 */
padding: 50px; /* 内边距:四周都是 50px */
margin: 40px; /* 外边距:四周都是 40px */
}

边框样式可选值:

值 效果
solid 实线
dashed 虚线
dotted 点线
double 双线
none 无边框

四边的值按”上右下左”顺时针来

1
2
3
4
5
.border-demo {
border-style: solid dashed dotted double; /* 上 右 下 左 */
border-width: 10px 30px 20px 40px; /* 上 右 下 左 */
border-color: purple;
}

这个顺时针规则适用于 margin、padding、border-width 等一系列属性,记住一次到处能用。顺便记一下简写规律:

写了几个值 含义
10px 四边都是 10px
10px 20px 上下 10px,左右 20px
10px 20px 30px 上 10px,左右 20px,下 30px
10px 20px 30px 40px 上 10px,右 20px,下 30px,左 40px

元素的真实宽度

标准盒模型下,元素占据的宽度不是 width,而是:

1
实际宽度 = width + padding-left + padding-right + border-left + border-right

所以 demo 里那个 width: 300px 的盒子,加上左右边框各 30px、40px,实际宽得多。

补充一个练习里没写、但很实用的属性:box-sizing: border-box。加上它之后,width 就代表包含 padding 和 border 的总宽,算尺寸时省事很多。现代项目几乎都会在样式开头全局设置:

1
2
3
* {
box-sizing: border-box;
}

margin 的一个经典现象

相邻的上下两个块级元素,如果都有 margin,它们之间不会相加,而是取较大值,这叫外边距合并(margin collapse)。所以给上面元素设 margin-bottom: 40px、下面元素设 margin-top: 30px,实际间距是 40px 而不是 70px。布局时算间距对不上,多半是这个原因。

小结

知识点 核心记忆点
导入方式 内联权重 1000 最高;内部和外部比的是顺序,不是类型
选择器 > 是直接子元素,空格是所有后代;: 伪类,:: 伪元素
display 行内元素不能设宽高,替换元素(img/input)例外;inline-block 兼顾两者
盒子模型 四边值按”上右下左”;实际宽度要加上 padding 和 border;上下 margin 会合并

下一篇进入布局,把浮动、定位和 Flex 放到一起对比——它们解决的其实是同一类问题,只是不同年代的方案。


本系列其他文章