在最近几个版本的浏览器的更新中,:has 选择器陆续登录了各大浏览器,而且基本都默认开启了,不用去打开各种开关,意味着它已经对普通用户全量开启。
:has 选择器其实之前已经存在一段时间了,但之前它只能在JS中使用,如 document.querySelector 函数,不是“实时选择器”,并不能在 CSS 中使用。
可以说 :has 选择器是到目前为止最为强大的选择器,没有之一,以往很难实现的效果,现在用它都可以纯 CSS 实现。
之所以说它最为强大,是因为它可以通过后面及里面的元素确定前面或外面的元素的CSS样式。在之前的 CSS 选择器中,几乎所有的选择器都被刻意设计成了只能由前面的元素确定后面的元素并设定它们的样式,比如div p这个选择器,是如果 p 的外面有 div 则 p 如何,div + p 则是 p 的前面的 div 则 p 如何,几乎没有“如果 p 的后面有 div 则 p 如何”这种语义的选择器,这让一些跟后面元素交互时改变前面元素的效果必须有 JS 的参与才能实现。
而有了 :has 选择器后,大多数的这些效果都可以只用 CSS 实现。
:has 选择可以根据一个相对选择器来选中某个元素,如
注意上面的三个选择器都可以不使用 div 而直接使用 :has()
接下来看简单的几个示例:
示例一、苹果Dock效果
Mac 系统默认底部的那个App Doc,在鼠标hover到一个项时,其前后两项也会变化
代码:JS Bin
效果:

关键代码为:
li:has(+li:hover), /* 选中被hover的li的前一个li,即当一个元素后面紧跟一个被hover的li时,选中它 */
li:hover + li { /* 选中被hover的li的后一个li,这个是大家都知道的 */
font-size: 1.5em;
width: 40px;
height: 40px;
}
示例二、只对 hover 时的目标元素增加效果,而不对其祖先元素增加效果
当多个元素嵌套时,只想为被鼠标直接 hover 的元素增加效果,而不想为其祖先元素增加效果,但 :hover 选择器会一同选中被直接 hover 元素的所有祖先元素,虽然也可以用标签名来加以限制,但如果标签名相同,就得增加类名来限制了,进而也就需要 JS 了。
比如我有一个需求,要对被 hover 的元素及其内部元素使用相同的背景颜色,而被 hover 的元素的外层,其实也被 hover 了,所以单用 :hover 选择器就无法完成,这时 :has 就派上用场了:
效果预览:
以下效果只让 hover 目标元素的边框变红,并让其内部的元素跟它一个背景色。

关键代码:
这个代码就有点难以解释了
/* 这个选择器选中 hover 的目标元素而不选中其祖先,.foo是统一加的类名,无需考虑 */
/* 直接使用:hover会选中它的所有祖先,所以加了一个 :not(:has(:hover)),匹配内部没有被hovering的元素
而hover目标元素,内部是没有被hover元素的,所以:not(:has(:hover))只会匹配hover目标
再结合一个:hover在它被hover时选中hover中的它
*/
div.foo:not(:has(:hover)):hover {
border-color: red;
border-width: 6px;
}
/*
理解了上面一个,这个就好理解了:
选中hover目标的内部所有元素,让背景颜色变透明,自然就看到了hover目标元素自身的背景
*/
div.foo:not(:has(:hover)):hover * {
background-color: transparent !important;
}
完整代码:JS Bin
示例三、根据子元素的数量使用不同的布局
有时,我们需要根据子元素数量的不同,来改变一些布局,比如,元素多的时候,每个元素小一点,而元素少的时候,每个元素大一些。
效果预览:

关键代码:
/* 意思是当ul中第二个子元素也是最后一个子元素时,选中ul里面的所有li,让它们的宽度为75,即半宽。
四个子元素时也做同样处理
*/
ul:has(li:nth-child(2):last-child) li,
ul:has(li:nth-child(4):last-child) li {
width: 75px;
height: 75px;
}
ul:has(li:nth-child(8):last-child) li {
width: 37.5px;
height: 37.5px;
}
/*
这个意思是当ul中正好有5个元素时,选中其中的前面两个,把宽度调为一半
*/
ul:has(li:nth-child(5):last-child) li:nth-child(-n + 2) {
width: 75px;
height: 75px;
}
代码:JS Bin
该方法还可以用另一种方法实现。
示例三、表格列高亮
一直以来,表格都很难实现列的高亮,从所周知,col代表表格的一列,但它是不能应用:hover效果的
一些解决方案是,在单元格被hover的时候,为其增加伪元素并定位以覆盖一整列的元素,再加上z-index让该元素现出在该列元素的下方,但这个方案也有不少问题,就不在这里细说了
用:has选择器可以轻松实现表格列的高亮,当然,即使表格用的grid布局实现,也可以轻松实现,但这里只说table实现的方案:
效果预览:

关键代码:
/* 行的高亮,直接给tr标签加hover即可 */
tr:hover {
background-color: magenta;
}
/*
列的高亮,好像也不“轻松”
table:has(td:nth-child(1):hover) td:nth-child(1)
意思是:
当table中有任何一个处于第一位置的的单元格被hover时,让此时table里的所有处于第一位置的单元格变色。
列高亮的逻辑就是,第一列的任何一个元素被hover,第一列的所有元素都应该亮起来,于是也就得到了上面的选择器。
复制一下把每一列的情况都写出来就可以了,好在这个代码只要复制足够多次就可以支持任意列数的表格。
*/
table:has(td:nth-child(1):hover) td:nth-child(1),
table:has(td:nth-child(2):hover) td:nth-child(2),
table:has(td:nth-child(3):hover) td:nth-child(3),
table:has(td:nth-child(4):hover) td:nth-child(4),
table:has(td:nth-child(5):hover) td:nth-child(5),
table:has(td:nth-child(6):hover) td:nth-child(6),
table:has(td:nth-child(7):hover) td:nth-child(7),
table:has(td:nth-child(8):hover) td:nth-child(8),
table:has(td:nth-child(9):hover) td:nth-child(9),
table:has(td:nth-child(10):hover) td:nth-child(10) {
background-color: magenta;
}
代码:JS Bin
当然,这个方案也有些缺点,表格如果每行单元格不一样多就无法实现了,当然,即使是行高亮,如果有单元格跨行也是会有问题的。
示例四、数独游戏的高亮效果
数独大家应该都玩过,如果能够把当鼠标所在行、列、和九宫格都高亮,对体验和解题都会有很大的提升
先看效果:

这个布局为了实现那些纵向的粗线,在表格中加入了col标签和colgroup标签,很多人可能不知道这个两个标签,它们可以写在tbody/thead标签的前面,一个col标签代表一列,若干个col标签放入colgroup标签中代表多个列,可以通过这两个标签方便的给列和列组加上边框或背景,不过它们并不能增加hover效果,所以并不能用这两个标签实现列的高亮。
而为了实现横向的粗线,表格每三个tr是放在一个tbody里的
然后让表格的边框合并,并给tbody和colgroup标签加上粗边框,就能看到相应的效果
单就数独这个布局和想要的高亮效果来讲,表格应该是最合适也是最方便实现的方案,grid布局应该也可以实现,但代码应该会更复杂。
html结构如下:
<table>
<colgroup>
<col>
<col>
<col>
</colgroup>
<colgroup>
<col>
<col>
<col>
</colgroup>
<colgroup>
<col>
<col>
<col>
</colgroup>
<tbody>
<tr>
<td>6</td>
<td> </td>
<td>5</td>
<td> </td>
<td>7</td>
<td>3</td>
<td>8</td>
<td>9</td>
<td> </td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td>7</td>
<td>2</td>
<td> </td>
<td>9</td>
<td> </td>
<td>3</td>
<td>6</td>
</tr>
<tr>
<td>3</td>
<td>8</td>
<td> </td>
<td>4</td>
<td>6</td>
<td>5</td>
<td>1</td>
<td> </td>
<td>2</td>
</tr>
</tbody>
<tbody>
<tr>
<td>1</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>2</td>
<td> </td>
<td> </td>
<td> </td>
<td>8</td>
</tr>
<tr>
<td> </td>
<td> </td>
<td>8</td>
<td>4</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>8</td>
<td> </td>
<td> </td>
<td>9</td>
<td>3</td>
<td>6</td>
<td> </td>
<td>4</td>
<td> </td>
</tr>
</tbody>
<tbody>
<tr>
<td>9</td>
<td>4</td>
<td>3</td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td>2</td>
<td>5</td>
</tr>
<tr>
<td> </td>
<td>6</td>
<td>8</td>
<td>3</td>
<td>9</td>
<td>2</td>
<td>4</td>
<td>1</td>
<td> </td>
</tr>
<tr>
<td>2</td>
<td> </td>
<td>1</td>
<td> </td>
<td>5</td>
<td>4</td>
<td> </td>
<td>8</td>
<td>9</td>
</tr>
</tbody>
</table>
列的高亮办法其实跟之前一样,但每个粗线分隔出来的九宫格的高亮,就有点复杂了
css代码如下,解释就在代码的注释里:
table {
font-family: consolas;
border-collapse: collapse;
}
select {
width: 100%;
}
colgroup,tbody {
border: 3px solid;
}
td {
width: 20px;
height: 20px;
text-align: center;
border: 1px solid;
}
tr:hover {
background-color: yellow;
}
/*
九宫格的高亮,代码比较多,但其实都是重复的,看懂一个其它就明白了,但确实也不好解释:
table:has(tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3):hover) tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3),
:has里面的选择器
tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3):hover
选中第一个tbody里(这里要用nth-of-type而不能用nth-child)第一到三个td被hover的时候
即知一个tbody里只有任何处于前三个的td被hover时,这个has就生效,
其中:nth-child(n + 1)选中第一个以上的元素,:nth-child(-n + 3)选中第三个以下的元素
再组合td和:hover选择器就选中了第一到三个td标签被hover的时候
所以选择器tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3):hover的含意为:
第一个tbody中(即前三行,请回看上面的html代码,前三行tr在第一个tbody里)任意一行的前三个td中的任意一个被选中时
此时我们需要将前三行每行的前三个单元格高亮,也就有了以下的代码
table:has(tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3):hover) tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3),
后半部分就是选中表格中第一个tbody中每行的前三个单元格:
tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3)
组合在一起就得到了完整的选择器。
另外的九个也就只是把数字修改一下。
*/
table:has(tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3):hover) tbody:nth-of-type(1) td:nth-child(n + 1):nth-child(-n + 3),
table:has(tbody:nth-of-type(1) td:nth-child(n + 4):nth-child(-n + 6):hover) tbody:nth-of-type(1) td:nth-child(n + 4):nth-child(-n + 6),
table:has(tbody:nth-of-type(1) td:nth-child(n + 7):nth-child(-n + 9):hover) tbody:nth-of-type(1) td:nth-child(n + 7):nth-child(-n + 9),
table:has(tbody:nth-of-type(2) td:nth-child(n + 1):nth-child(-n + 3):hover) tbody:nth-of-type(2) td:nth-child(n + 1):nth-child(-n + 3),
table:has(tbody:nth-of-type(2) td:nth-child(n + 4):nth-child(-n + 6):hover) tbody:nth-of-type(2) td:nth-child(n + 4):nth-child(-n + 6),
table:has(tbody:nth-of-type(2) td:nth-child(n + 7):nth-child(-n + 9):hover) tbody:nth-of-type(2) td:nth-child(n + 7):nth-child(-n + 9),
table:has(tbody:nth-of-type(3) td:nth-child(n + 1):nth-child(-n + 3):hover) tbody:nth-of-type(3) td:nth-child(n + 1):nth-child(-n + 3),
table:has(tbody:nth-of-type(3) td:nth-child(n + 4):nth-child(-n + 6):hover) tbody:nth-of-type(3) td:nth-child(n + 4):nth-child(-n + 6),
table:has(tbody:nth-of-type(3) td:nth-child(n + 7):nth-child(-n + 9):hover) tbody:nth-of-type(3) td:nth-child(n + 7):nth-child(-n + 9) {
background-color: yellow;
}
/* 列的高亮,跟前面一个案例一样,这里就不解释了 */
table:has(td:nth-child(1):hover) td:nth-child(1),
table:has(td:nth-child(2):hover) td:nth-child(2),
table:has(td:nth-child(3):hover) td:nth-child(3),
table:has(td:nth-child(4):hover) td:nth-child(4),
table:has(td:nth-child(5):hover) td:nth-child(5),
table:has(td:nth-child(6):hover) td:nth-child(6),
table:has(td:nth-child(7):hover) td:nth-child(7),
table:has(td:nth-child(8):hover) td:nth-child(8),
table:has(td:nth-child(9):hover) td:nth-child(9) {
background-color: yellow;
}
完整代码:JS Bin
以上数独的最终答案为:


