前言
作为一名开发者,在看到设计图就应本能地在脑海中组织好大概的CSS代码用于排版与布局网页。虽然样式看上去很简单,但在编写样式时肯定会遇到以下情况。
样式覆盖导致排版错位甚至乱套- 布局时不知使用什么长度单位声明属性
- 引入第三方
UI框架导致自定义样式失效 - 同一节点同时出现多种类型样式时不知如何优雅解决
这些都是开发项目时的常见问题,我相信很多同学在遇到样式覆盖的问题时一言不合就使用!important暴力解决,这样真的好吗?
优先级别
在编写CSS时,使用不同选择器选中相同节点且为相同样式声明不同值,就会发生样式覆盖。发生样式覆盖时,应用哪个样式就由选择器的优先级别决定,因此样式覆盖的根本原因是未处理好规则间的优先级别。虽然使用!important能解决问题,但不能什么情况都由!important暴力解决。
为何样式有优先级别之分?当创建的样式越来越复杂,一个节点的样式将会受到越来越多的影响,该影响可能来自周围节点也可能来自自身。通过相关规范分配给规则一个权重,那样式可根据权重计算,呈现网页最终效果。
从以下方面了解优先级别,相信能更好把握优先级别的场景,通过优先级别解决样式覆盖的问题。
特性
优先级别具备以下特性,在熟练编写CSS后就能很易发现这些特性的存在。
- 就近原则:后出现的样式其优先级别比先出现的样式更高
- 继承样式:优先级别最低
- !important样式:优先级别最高,若冲突则重新计算
- 引入权重:
内联样式 > 内嵌样式 > 外部样式 > 导入样式
就近原则、继承样式和!important样式都很易理解,那引入方式该如何理解?那看看它们是如何表现的,需从文件层面理解。
内联样式
内联样式指在HTML标签中使用style声明样式,是最简单粗暴的样式声明方式。通常不推荐这样声明样式,它会导致HTML结构变得不纯净且体积变大,不利于SEO,也不利于后期维护。
<p style="color: #f66;">I am JowayYoung</p>
内嵌样式
内嵌样式指在<head>中使用<style>声明样式。通常在开发环境中使用,像webpack使用style-loader时就是将代码中的样式全部提取出来,以该方式声明网页样式。若多个网页使用公共样式,就要重复声明,网页数量越多就越难维护,当然网页样式较少的情况下还是使用该方式最好。
<head>
<style>
p {
color: #f66;
}
</style>
</head>
外部样式
外部样式指在<head>中使用<link>引入样式,是最方便快捷的样式声明方式。通常在生产环境中使用,这样方便地将HTML代码与CSS代码完全分离,为HTML语义化与结构与表现完全分离提供技术上的支持。这样的处理不仅利于开发也利于维护,同时也是团队协作的最优CSS代码组织方式。
<head>
<link rel="stylesheet" href="path/to/main.css">
</head>
导入样式
导入样式指在CSS类型文件中通过@import导入其他样式。导入样式其实与外部样式很相似,在HTML初始化时,会被导入到文件中成为文件的一部分。对于多个网页的公有样式,可将它们抽离出来,再通过@import导入样式到这些网页的css文件中。
<head>
<style>
@import url("path/to/common.css");
</style>
</head>
@import url("path/to/common.css");
通过代码再结合就近原则,是不是更易理解?内联样式直接作用在节点中,因此优先级别最高。内嵌样式附属于html文件的解析而解析,因此优先级别跟着内联样式。外部样式需加载完毕才能解析,因此优先级别跟着内嵌样式。导入样式作用于内联样式或外部样式中,因此优先级别跟着外部样式。
最终就有了内联样式 > 内嵌样式 > 外部样式 > 导入样式的优先级别排序。
权重
上述都是基于文件层面的理解,那进入文件中所有样式都使用不同选择器声明,那如何区它们的分优先级别?选择器有着明显不可逾越的等级制度,可将其划分为六个权重等级。每个等级间的优先级别差距不可逾越,这些等级又称为权重。
以下从直观权重与微观权重两方面表述。简而言之,数字组成的值越大其权重就越大。
直观权重
- 10000:
!important - 1000:
内联样式 - 100:
ID选择器 - 10:
类选择器、伪类选择器、属性选择器 - 1:
标签选择器、伪元素选择器 - 0:
通配选择器、兄弟选择器、后代选择器
微观权重
- 1,0,0,0,0:
!important - 0,1,0,0,0:
内联样式 - 0,0,1,0,0:
ID选择器 - 0,0,0,1,0:
类选择器、伪类选择器、属性选择器 - 0,0,0,0,1:
标签选择器、伪元素选择器 - 0,0,0,0,0:
通配选择器、兄弟选择器、后代选择器
总体来说直观权重与微观权重只是表达方式不同,但实际意义一样,使用公式可表达为以下形式。
!important > 内联样式 > ID选择器 > 类选择器 = 伪类选择器 = 属性选择器 > 标签选择器 = 伪元素选择器 > 通配选择器 = 兄弟选择器 = 后代选择器
计算
认识了优先级别的特性与权重,可通过它们计算出以下常见场景的样式。
优先级别相同的规则使用最后出现的规则
input {
padding: 0 10px;
border: none;
border-radius: 5px;
height: 30px;
font-size: 16px;
color: #fff;
}
input.input-box {
background-color: #f66;
}
input:focus {
background-color: #66f;
}
input[type=text] {
background-color: #f90;
}
虽然类选择器、伪类选择器和属性选择器三者的优先级别相同,但最后出现的规则其优先级别最高,所以<input>的背景颜色最终会显示#f90。
优先级别无视节点在DOM树中的距离
html h1 {
color: #f66;
}
body h1 {
color: #66f;
}
虽然<html>包括着<body>,但根据就近原则,所以<h1>的颜色最终会显示#66f。
不同规则作用于相同节点使用优先级别最高的规则
#bruce {
color: #f66;
}
[id=bruce] {
color: #66f;
}
虽然两者规则都作用于ID为bruce的<h1>,但ID选择器的优先级别比属性选择器高,所以<h1>的颜色最终会显示#f66。
:not()不参与优先级别的计算
:not()在优先级别计算中不会被看作伪类,但会把:not()中的选择器当作普通选择器计数。简而言之,忽略:not(),其他伪类照常参与优先级别计算。
通过上述示例可得到以下规则。
- 规则的权值不同时,权值高的规则优先
- 规则的权值相同时,后定义的规则优先
- 属性后面追加
!important时,规则无条件绝对优先
长度单位
粗糙的干活可能只需px与%两个长度单位,随着终端设备分辨率的多样性提高,CSS衍生出越来越多长度单位,灵活结合这些长度单位能为网页的布局方案提供更多可能性。
| 单位 | 定义 | 类型 | 描述 |
|---|---|---|---|
| px | 像素 | 绝对单位 | - |
| pt | 点 | 绝对单位 | 1pt = 1/72in |
| pc | 派 | 绝对单位 | 1pc = 12pt |
| mm | 毫米 | 绝对单位 | - |
| cm | 厘米 | 绝对单位 | - |
| in | 英寸 | 绝对单位 | 1in = 96px = 2.54cm |
| % | 百分比 | 相对单位 | 相对父节点尺寸,宽度相应,高度不一定相应 |
| em | M的宽度 | 相对单位 | 相对当前节点字体 |
| rem | M的宽度 | 相对单位 | 相对根结点字体 |
| ch | 0的宽度 | 相对单位 | 相对当前节点字体 |
| ex | x的宽度 | 相对单位 | 相对当前节点字体 |
| vw | 1%视窗宽度 | 相对单位 | 相对视窗 |
| vh | 1%视窗高度 | 相对单位 | 相对视窗 |
| vmin | vw/vh最小者 | 相对单位 | 相对视窗 |
| vmax | vw/vh最大者 | 相对单位 | 相对视窗 |
这么多单位,到底如何区别?首先要明确一点,那就是屏幕分辨率。
屏幕分辨率
屏幕分辨率指横纵向中的像素点数,单位是px。屏幕分辨率确定计算机屏幕中能显示多少信息,以水平与垂直像素衡量。在屏幕尺寸一样的情况下,屏幕分辨率越低在屏幕中显示的像素越少,单个像素尺寸也较大,屏幕分辨率越高在屏幕中显示的像素越多,单个像素尺寸也较小。
屏幕分辨率就是屏幕中显示的像素个数,分辨率1920×1080意味着水平方向含有1920个像素数,垂直方向含有1080个像素数。在屏幕尺寸一样的情况下,屏幕分辨率越高,显示效果越细腻。这也是为何iPhone经常亮瞎眼睛的原因。
在同一网页中以px作为长度单位时,在不同屏幕分辨率中显示的大小可能不同。在低屏幕分辨率中像素较大,显示的网页元素也偏大偏模糊。实际上所有单位无论是绝对单位还是相对单位,最终都会转化为px在屏幕中显示,因此在设计与开发时都以px为准。
em/rem区别
em与rem是移动端布局中特有的长度单位,两者的后缀都一样。rem全称是root em,指相对根节点作为参考的长度单位。
- em:当前节点字体宽度,准确来说是一个
M的宽度 - rem:默认字体宽度,准确来说是一个
M的宽度
两者区别在于:em相对父节点,rem相对根节点。em以当前节点字体宽度作为参考,rem以根节点<html>字体宽度作为参考,默认是16px。很多同学错误地以为em是根据父节点作为参考,实际上是当前节点继承了父节点的属性后产生的错觉。
em与rem都是很灵活且可扩展的长度单位,由浏览器自行转换为px,具体取决于设计图中的字体大小。
针对移动端,我通常会结合JS根据屏幕宽度与设计图宽度的比例动态声明<html>的font-size,以rem为长度单位声明所有节点的几何属性,这样就能做到很多移动设备的网页兼容,兼容出入较大的地方再通过媒体查询做特别处理。
function AutoResponse(width = 750) {
const target = document.documentElement;
if (target.clientWidth >= 600) {
target.style.fontSize = "80px";
} else {
target.style.fontSize = `${target.clientWidth / width * 100}px`;
}
}
AutoResponse();
前提还需在<html>中声明以下代码,阻止用户缩放屏幕。
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">
视窗比例单位
在CSS3中增加了四个与viewport相关长度单位。随着时间推移,目前很多浏览器对这些长度单位都有较好的兼容,这也是未来最建议在伸缩方案中使用的长度单位。
- 1vw:
1%视窗宽度 - 1vh:
1%视窗高度 - 1vmin:
1%视窗宽度与1%视窗高度中最小者 - 1vmax:
1%视窗宽度与1%视窗高度中最大者
视窗宽高在JS中分别相应window.innerWdith与window.innerHeight。若不考虑低版本浏览器的兼容,完全可用一行代码秒杀所有移动端的伸缩方案。
/* 基于UI width=750px DPR=2的网页 */
html {
font-size: calc(100vw / 7.5);
}
这是calc()的一个神操作,在第7章会详细讲述calc()怎样玩。这行代码就留给大家思考,为何能这样处理,细心的同学可能发现这段代码可代替上述那段JS代码。

#root {
background: red;
}
id="root" style="background:blue"
最终显示蓝色