:nth-child() 的 a 是步长(非零整数,决定间隔),b 是偏移(整数,决定起始位置,但结果须≥1);n 为≥0整数变量,代入 an+b 计算匹配序号。

什么是 :nth-child() 的 A 和 B
它不是“第几个孩子”的简单计数,而是线性公式 An+B 的求值结果——每次代入整数 n = 0, 1, 2, 3...,算出一个序号,匹配对应位置的元素(从 1 开始计)。
比如 :nth-child(2n+1): n=0 → 1,匹配第 1 个; n=1 → 3,匹配第 3 个; n=2 → 5,匹配第 5 个…… 所以是所有奇数位子元素。
-
A是步长(必须是整数,可正可负,不能为 0),决定“隔几个” -
B是偏移(可正可负可零),决定“从哪开始”,但最终匹配位置必须 ≥ 1 -
n不是索引,只是生成器变量,只取 ≥ 0 的整数,不手动指定 -
:nth-child(-n+3)这种写法合法:它等价于匹配第 1、2、3 个(因为n=0→3,n=1→2,n=2→1,n=3→0无效),常用来选前 N 项
:nth-child() 和 :nth-of-type() 完全不是一回事
前者看的是父元素下「所有同级兄弟」的顺序位置,后者只看「同标签名」的兄弟顺序。这是最常混淆、也最容易导致样式错位的地方。
- 如果父元素里混着
<p></p>、<div>、<code><span></span>,:nth-child(2)指的是第二个子节点,不管它是什么标签 - 而
:nth-of-type(2)指的是第二个<p></p>(假设选p:nth-of-type(2)),跳过中间所有非<p></p>元素 - 在表格或列表中混用注释、空文本节点、动态插入的占位符时,
:nth-child()行为会突然变化,但:nth-of-type()更稳定 - 没有
:nth-of-class()—— 类名无法参与这类计数,别指望靠加 class 来“重置”序号 - 空格或换行产生的文本节点会被计入子元素总数(尤其在
<ul></ul>内写换行时),导致:nth-child(2)实际匹配到一个看不见的文本节点,而非你想要的<li> - css 优先级不够:如果其他规则用了
!important或更具体的选择器(如ul li.active),你的:nth-child()可能被覆盖 - 伪类不支持嵌套计算:不能写
div :nth-child(2) span去“找第二项里的 span”,它只作用于直接子元素的定位 - 浏览器兼容性没问题(IE9+ 都支持),但旧版 safari 对负系数(如
-2n+5)解析偶有偏差,建议用-n+5替代-1n+5避免歧义
常见失效场景和调试技巧
写对了公式却没生效?大概率是选择器权重、dom 结构或伪类限制惹的祸。
用 js 动态验证 :nth-child() 匹配逻辑
光靠眼睛数容易错,尤其结构复杂时。用几行 JS 就能实时看到哪些元素被命中:
立即学习“前端免费学习笔记(深入)”;
Array.from(parent.children).forEach((el, i) => { const n = i + 1; const matches = (A, B) => (A * Math.floor((n - B) / A) + B) === n && (n - B) % A === 0 && n >= 1; console.log(`第 ${n} 个: ${matches(2, 1) ? '✓' : '✗'}`); });
不过更实用的是直接查:
- 打开 DevTools,在 Elements 面板右键目标元素 → “break on” → “Attribute modifications”,观察是否被其他脚本动态改 class/structure
- 临时加一条高亮规则:
*:nth-child(2n) { outline: 2px solid red !important; },快速确认公式是否按预期触发 - 注意:JS 的
element.matches(':nth-child(2n+1)')在大多数现代浏览器中可用,但不要在循环里高频调用,性能差
真正难的不是写出公式,而是意识到:你写的那个“第 n 个”,到底是在哪个 DOM 层级、哪种节点构成下成立的。一旦结构微调,:nth-child() 就可能全线偏移。