网页上的开关状态,CSS伪类:open都有了,怎么就是没有:closed?

2,455字
10–16 分钟
in

摘要

目录

写CSS样式的时候经常碰到那种能展开能收起的玩意儿,比如点击一下才显示详情的<details>元素。以前想给收起状态加样式,总觉得应该有个:closed伪类直接搞定。结果翻了一圈资料才发现,这东西压根儿就没进规范。那咋整?其实:not(:open)这招完全够用,还能避免伪类滥用。这篇文章就唠唠:open伪类是干啥的,咋用它,以及在没有:closed的情况下,怎么用:not()来精准命中那些关着的元素。顺带还扒了扒CSS工作组关于这事的讨论,看看到底为啥不整个对应的:closed

:open到底是啥玩意儿

这东西说白了就是个状态检测器。浏览器里有些元素天生就带“开”和“关”两种状态,比如<details>点一下就展开,再点一下又收回去。:open这个伪类就是专门逮那些当前处于打开状态的元素。

举个例子,<details>没点开的时候啥事儿没有,一旦点开让它显示里面藏的内容,:open就立马生效。要理解这个,先看看它长啥样:

details:open {
  border-left: 4px solid #f90;
  background-color: #fff5e6;
}

这段代码的意思是,但凡页面上的<details>处在打开状态,左边框就冒出一条橙色高亮,背景也跟着变浅橙色。要是关上了,这些样式就自动消失。

这玩意儿不只是<details>能用,像<select>的下拉列表打开的时候,:open也能命中的。不过各个浏览器支持程度不太一样,写代码的时候得留个心眼。

没:closed,照样拿捏

看到这儿可能有人要问了:那我想给关上状态加样式咋整?是不是应该有个:closed来配合一下?

理想很丰满,现实是这玩意儿到现在都还没写进规范。CSS工作组讨论了好一阵子,最后还是决定先只搞:open:closed暂时搁置。

但这事儿难不住人,CSS有个逻辑组合伪类:not(),反转一下判断条件就行:

details:not(:open) {
  opacity: 0.7;
  filter: grayscale(0.3);
}

这代码的意思就是“如果这个details不是打开状态,那就给它加点灰色调”。逻辑上完全成立,而且:not()的兼容性比:open还好,老浏览器也基本都能认。

:not(:open)的时候,有两点得留意:第一,它会匹配所有不是打开状态的元素,包括那些根本就没开闭状态的元素。比如写div:not(:open)是没意义的,因为div本来就没有开闭概念,:open对它永远不成立,所以:not(:open)就永远为真。第二,:not()里面可以塞各种复杂选择器,但写得太绕会让代码可读性打折扣。

实战演练:一个带开关状态的筛选面板

纸上谈兵没意思,直接整一个实际场景。假设页面上有个筛选面板,默认是收起来的,点一下“显示筛选”按钮才展开。展开的时候背景要高亮,收起来的时候给个半透明效果。

<details class="filter-panel">
  <summary>显示筛选条件 ⚙️</summary>
  <div class="filter-content">
    <label><input type="checkbox"> 价格从低到高</label>
    <label><input type="checkbox"> 包邮</label>
    <label><input type="checkbox"> 品牌官方</label>
  </div>
</details>

对应的样式可以这么写:

.filter-panel {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 8px;
  transition: all 0.2s ease;
}

/* 打开状态:背景刷成浅绿,边框加个高亮 */
.filter-panel:open {
  background-color: #e6f7e6;
  border-color: #2c7a2c;
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}

/* 关闭状态:背景灰一点,整体淡一点 */
.filter-panel:not(:open) {
  background-color: #f5f5f5;
  opacity: 0.9;
}

/* 让summary在打开时图标有点变化 */
.filter-panel:open summary {
  font-weight: bold;
  color: #2c7a2c;
}

这里:open:not(:open)配合起来,把两种状态下的视觉差异拉满。用户点开的时候能明显感知到面板“活”了,收起来的时候也不会显得太突兀。

在调试的时候可能会发现一个问题::not(:open)的优先级跟:open是一样的,如果两个块里写了冲突的样式,得靠代码顺序来决定谁覆盖谁。一般建议把:open的样式写在后面,或者给关键属性加上!important(不过这个操作要慎重,用多了后面维护起来头疼)。

别死磕:closed,组合拳更香

场景推荐写法优点
选中打开的元素:open语义清晰,直观
选中关闭的元素:not(:open)兼容性好,逻辑自洽
同时区分两种状态:open + :not(:open)覆盖全面,不依赖未实现伪类
排除无关元素:is(details, select):not(:open)精确限定范围

CSS工作组当初讨论:closed的时候,争议点主要在于“<div>这种不能开关的元素,到底算不算:closed”。如果把:closed定义成“能开关且当前是关闭状态”,那:closed就等于:not(:open)加上类型限制,实现上反而复杂了。直接用:not(:open),虽然多打了几个字符,但逻辑更干净,不会有歧义。

实际开发中,:not(:open)写多了可以考虑封装成工具类,比如:

.closed-state {
  /* 通用关闭状态样式 */
}

details:not(:open).closed-state,
select:not(:open).closed-state {
  /* 具体样式 */
}

这样既能复用,又不会影响其他元素。