算出胜出的那条规则
目标
CSS 看起来不可预测,不是因为没有规则,而是规则分为三个阶段:重要性 → specificity → 顺序。
这里没有浏览器,因此不会评分“视觉效果是否正确”。我们会实际计算哪条规则获胜——即使没有浏览器,答案也只有一个。
要创建的内容
只需创建 /root/work/css/styles.css,不需要 HTML。
评分器会假设存在以下元素并计算 cascade。
div#app > div.card
main.lessonLayout > aside.lessonSide
main.lessonLayout.sideHidden > aside.lessonSide
div.toolbar
计算器
cd /root/work/css
python3 /opt/lab/checks/css-lab/cascade.py styles.css
它会显示每条规则的顺序、媒体条件与 specificity。支持标签、类、ID、伪类、后代与子代组合器、逗号分组,以及 @media (max-width|min-width: Npx)。遇到其他选择器时,不会静默跳过,而会明确提示“无法解析”。
步骤
- 3 个
:roottoken + 3 处var() - 预测 specificity →
02-spec.txt - 不使用
!important也能获胜 - 顺序陷阱(把默认规则放在媒体查询上方)
- 三列网格
- 折叠后变为一列
- flex 工具栏
- 运行计算器 →
08-cascade.txt - 总结 →
09-notes.md
参考
第 4 步和第 6 步都是该服务真实发生过的缺陷:移动端目录遮罩无法打开,以及折叠目录后页面增长到 33,000px。
把 token 放在 :root 中
创建 /root/work/css/styles.css,在 :root 中定义至少三个自定义属性,再通过 var() 在至少三处使用它们。
mkdir -p /root/work/css。名称可以自由选择,如 --accent、--gap、--fg。与 SCSS 变量不同,自定义属性在运行时仍然存在并会继承,因此可以只在某棵子树中覆盖。
计算 specificity
把下面五个选择器的 specificity 逐行写入 02-spec.txt,格式为 번호=a,b,c。
1) a
2) .btn.primary
3) #nav a:hover
4) ul > li::marker
5) :where(.card) .title
a=ID,b=类、属性与伪类,c=标签与伪元素。例如 1=0,0,1。第 5 项是陷阱::where() 的 specificity 始终为 0。还要记住各位不会进位:11 个类也无法胜过 1 个 ID。
不使用 !important 也能获胜
先写 .card { color: ... },再让 #app 内的 .card 使用另一种颜色。不能使用 !important。
评分器会实际计算 div#app > div.card 元素上哪条规则获胜。#app .card 为 1,1,0,会胜过 .card(0,1,0)。!important 只能由另一个 !important 击败,一旦使用,以后就必须一直这样处理该属性。
避开顺序陷阱
让 .lessonSide 的 position 默认是 sticky,在 max-width: 900px 时变为 fixed。两条规则 specificity 相同,因此顺序决定答案。
把默认规则放在 @media 块上方。若放在下方,因为 specificity 相同(都是 0,1,0),后写的默认规则在移动端也会获胜。评分器会分别在 1280px 与 500px 宽度计算。该服务的移动端遮罩无法打开,正是由这个缺陷导致。
用网格构建骨架
把 .lessonLayout 设为 display: grid,并指定三列的 grid-template-columns 与 gap。
例如 grid-template-columns: 240px 1fr 200px。需要同时定义行与列时使用 grid;若只是在一行内分配空间,flex 更合适。
折叠时列也必须消失
在 .lessonLayout.sideHidden 时让布局变为一列。不能像 0 1fr 200px 那样留下宽度为 0 的列。
子元素为 display: none 时,会从网格中完全移除。若只把列设成 0,正文会被挤进 0px 列,导致每个字符都换行;真实页面曾因此增长到 33,000px。请减少列的数量,使用 grid-template-columns: 1fr。
工具栏使用 flex
把 .toolbar 设为 display: flex,并同时指定 justify-content、align-items 与 gap。
最常见的组合是 space-between + center + gap。若只想把一个元素推到右侧,也可以给它设置 margin-left: auto。
用计算器确认
使用 /opt/lab/checks/css-lab/cascade.py 解析样式表,并把结果保存到 08-cascade.txt。
python3 /opt/lab/checks/css-lab/cascade.py styles.css | tee 08-cascade.txt。输出会显示每条规则的顺序、媒体条件与 specificity。这正是看到开发者工具中带删除线规则时,大脑所做的计算。
解释为何失败
在 09-notes.md 中至少写三行:第 4 步颠倒顺序会发生什么,第 6 步保留 0 宽列会发生什么,以及想使用 !important 时应先检查什么。
正文中必须包含 특정성、순서、그리드。诊断始终分四步:选择器是否匹配、规则是否正在失败、原因是 specificity 还是顺序、是否错误期待继承。