网页元素开关状态,到底咋用CSS精准拿捏?:open和:closed的江湖恩怨

2,681字
11–17 分钟
in

摘要

目录

写页面时,经常碰到像下拉框、折叠面板这类能开能关的玩意儿。以前想给关上的状态写样式,得绕个弯子用:not(:open),读起来就跟念绕口令似的。最近CSS社区为这事儿吵得挺凶,琢磨着要不要加个:closed伪类。咱今儿就扒一扒这个:open是咋用的,再聊聊没有:closed的时候,怎么用:not(:open)把活儿干漂亮,顺便看看这哥们儿到底该不该有。

啥是:open

:open这玩意儿,就是专门用来逮那些能打开、能关上的元素,并且是在它们处于“打开”状态的时候下手。好比说那个能点开收起的<details>标签,还有那个下拉菜单<select>,只要它们处在展开或者弹出的状态,:open就能精准命中。

/* 给打开的详情区域换个皮肤 */
details:open {
  background: #f0f0f0;
  border-left: 4px solid #06c;
}

这段代码一上,只要<details>展开,背景立马变灰,左边还带条蓝边,跟装了感应器似的。不过得留个心眼,这玩意儿刚出来那会儿,Safari浏览器还没跟上,所以写样式的时候最好先测测,别翻车。

用:open搞定弹出层样式

实际操作里头,:open最常用来对付那些临时蹦出来的家伙。比如做一个自定义的下拉选择框,点开的时候得给点特殊待遇。

<select class="fancy-select">
  <option>摸鱼模式</option>
  <option>爆肝模式</option>
  <option>躺平模式</option>
</select>
.fancy-select:open {
  border-color: #f90;
  box-shadow: 0 0 0 3px rgba(255, 153, 0, 0.25);
  background-color: #fff9e6;
}

这么一搞,下拉框打开的时候,边框变橙黄,还带一圈光晕,背景也跟着暖起来,用户一下就知道这玩意儿能选东西。不过有个小坑,:open只管“打开”那一刻的状态,要是想给“关上”的时候也搞点花样,就得另寻出路。

没有:closed的日子,:not(:open)来顶班

既然现在还没个:closed能用,那想摸到关上的元素,就得靠:not(:open)这个反向操作。别看它多写几个字,用熟了也挺顺手。

流程一:给关上的折叠面板加个暗淡效果

有时候希望那个<details>收起来的时候,文字颜色淡一点,显得不那么起眼,等展开了再精神起来。这活儿就得:not(:open)来干。

  1. 搭个基础架子:先弄个普通的<details>框,里头放点内容。
<details class="info-card">
  <summary>点击瞅瞅干货</summary>
  <p>里面全是硬核内容,平时藏着掖着。</p>
</details>
  1. 写样式先管展开的:按照正常逻辑,把展开时的样式写出来,比如边框、背景、字体都整精神点。
.info-card:open {
  border: 2px solid #06c;
  background: #e6f4ff;
  padding: 1rem;
}
  1. 再写关上的样式:用:not(:open)把关上的状态逮住,让它看起来蔫儿一点。
.info-card:not(:open) {
  opacity: 0.7;
  filter: grayscale(0.2);
}

这么一来,页面上一排折叠面板,没打开的都灰扑扑的,一眼就能看出哪个是收着的哪个是展开的。写这步的时候有个关键点,:not(:open)会匹配所有不是打开状态的东西,包括那些压根不能开关的元素。所以最好给目标元素加个类名,比如.info-card,精准定位,别误伤友军。

流程二:给自定义选择器整点关闭提醒

做个表单的时候,下拉框没选东西之前,也就是关闭状态,可以加个淡入淡出的提示,让用户知道该点它了。

  1. HTML结构:一个普通的<select>,配个提醒的文案。
<div class="select-wrapper">
  <select class="theme-select">
    <option value="">-- 选个皮肤 --</option>
    <option>暗黑</option>
    <option>明亮</option>
  </select>
  <span class="hint">点我选皮肤呀~</span>
</div>
  1. 隐藏提示,等关闭时再出现:正常情况下,让提示藏起来,等下拉框关上(也就是没打开)的时候,再让它幽幽地显示。
.hint {
  display: none;
  font-size: 0.8rem;
  color: #999;
  margin-top: 0.25rem;
}

.theme-select:not(:open) ~ .hint {
  display: block;
  animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

效果就是,下拉框一关上,底下就冒出一行小字“点我选皮肤呀~”,一点开就消失。这样做用户体验感拉满,比干巴巴地杵在那儿强多了。不过得注意,:not(:open)<select>上的支持,不同浏览器可能有点时间差,写代码前最好翻翻最新的兼容性表格。

江湖传说:closed到底该不该有

说到这儿,肯定有人嘀咕,搞这么复杂,直接来个:closed不就完事了?其实这事儿在CSS社区里吵了两年多。2022年那会儿,大伙儿刚开始琢磨:open,后来有人一拍大腿,觉得有开就得有关,对称才美。但一深入讨论,问题来了,如果有了:closed,那像<div>这种永远关不上的元素,:closed到底算不算它?算的话逻辑就乱了,不算的话,又得在规范里给它开小灶。

那帮搞标准的也是头大,最后2024年底暂时拍板,先只整:open:closed的事儿搁置。但同时也透了个口风,未来有可能改。所以目前最稳当的玩法,还是用:not(:open)先顶着。这俩写法在逻辑上完全等价,就像问“你不是那个意思?”跟“你是另一个意思?”在语义上有点微妙差别,但最终指向一样。

其实用:not(:open)还有个隐藏好处,那就是强迫自己写选择器的时候,必须明确知道在选啥。比如想给所有不是打开状态的<details>加样式,就得写details:not(:open),脑子里自然就会过滤掉那些无关元素。要是真来个:closed,可能一不留神就写个*:closed,那乐子就大了,页面里所有不能开关的东西全被套上样式,直接翻车。