先记住这个答案
:only-child 匹配的是父元素里唯一的子元素。如果有任何兄弟元素,不管什么类型,都不匹配。:only-of-type 宽松一些,只看同标签类型的兄弟,有其他类型的兄弟没关系。比如一个 <div> 里有一个 <span> 和一个 <p>,<span> 不匹配 :only-child(因为有兄弟 <p>),但匹配 span:only-of-type(因为 <span> 类型只有它一个)。
:only-child要求零个兄弟:only-of-type只看同标签类型:only-child等价于同时满足首尾- 文本节点不算兄弟元素
用一个例子看清区别
假设页面上有一个卡片组件,内容区域有时只有一段文字,有时既有标题又有文字。对单段文字的情况,想让它居中显示;有标题时保持正常排版。这正好用 :only-child。
/* 只有一个子元素时让它居中 */
.card-body > :only-child {
text-align: center;
margin: auto;
}
/* 有标题和段落两个子元素时,标题左对齐 */
.card-body > h3 {
text-align: left;
}如果 .card-body 里只有一个 <p>,它匹配 :only-child,文字居中。如果同时有 <h3> 和 <p>,两个元素都不是唯一的子元素,:only-child 对谁都不生效。
什么时候该用 only-of-type
:only-of-type 的典型场景是父元素里混了不同标签。比如一个列表项里有图标和文字两部分,图标是 <img>,文字是 <span>。想在没有图标时让文字撑满整行,可以用 span:only-of-type——因为它只看 <span> 类型的兄弟数量,不管有没有 <img>。
/* 列表项里只有一个 span 时撑满宽度 */
.list-item span:only-of-type {
display: block;
width: 100%;
}即使 .list-item 里还有一个 <img>,只要 <span> 只有一个,这条规则就生效。换成 span:only-child 的话,有 <img> 在就不匹配了。
这里有个容易忽略的点::only-of-type 看的是 HTML 标签类型,不是 CSS 类名。两个 <span> 即使类名不同,也互为同类型兄弟,让 :only-of-type 失效。
容易答错的地方
- 以为文本节点也算兄弟
- 一个
<div>里写了一段文字加一个<span>,很多人以为<span>有个文本兄弟所以不匹配:only-child。其实文本节点不是元素节点,:only-child只数元素,所以<span>仍然匹配。要判断有没有文本节点在旁边,只能用 JavaScript。 - 以为 only-of-type 看的是类名
:only-of-type对比的是标签名,比如<div>、<span>、<p>,不是.active、.item这类类名。两个<span class="a">和<span class="b">,虽然类名不同但标签相同,:only-of-type不会匹配其中任何一个。
面试官还会怎么问?
only-child 等价于哪两个伪类的组合?
:only-child 等价于 :first-child:last-child。只有同时是第一个又是最后一个子元素,才说明没有其他兄弟。同理 :only-of-type 等价于 :first-of-type:last-of-type。
动态增删子元素时,only-child 会实时更新吗?
会。CSS 伪类是根据当前 DOM 状态实时匹配的。如果用 JavaScript 往父元素里插入了一个新的子元素,原来匹配 :only-child 的元素会立刻失去匹配,浏览器会重新计算样式。
怎么选中恰好有两个子元素时的第一个?
用 :first-child:nth-last-child(2) 可以做到。:nth-last-child(2) 表示从末尾数第二个,如果第一个子元素同时也是倒数第二个,说明一共恰好两个。这是结构伪类组合使用的经典技巧。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。