用HTML的details name属性实现互斥手风琴,连JS都不用写

2026-08-27 0 521

以前想做“手风琴”折叠面板,第一反应就是写JavaScript或者引一个组件库。一堆class切换、事件绑定动不动就几十行。但是最近我试了一下HTML原生的<details>元素加name属性,发现浏览器已经帮你把手风琴逻辑写好了。只需要一个name,同一个name下的<details>会自动互斥:开一个另一个就关掉,完全不需要脚本。

这个特性其实已经悄悄进了Chrome 120,现在Safari也跟进了。虽然还不能在老旧浏览器上使用,但做一个内部工具或者移动端页面已经足够了。我自己拿它改造了一个FAQ页面,原来绑了一堆点击事件,现在全部删掉,代码肉眼可见地变轻了。

一个最普通的details长什么样

日常用法大家应该不陌生:

<details>
  <summary>什么是details?</summary>
  <p>details是一个HTML元素,默认隐藏里面的内容,点击summary展开。</p>
</details>

这样就是一个独立可展开的折叠块。但是每个<details>都是各管各的,你可以同时打开多个。想要做成“只能打开一个”的手风琴效果,就得用JS把其他的全部关掉。现在不用了,给details加上name就行。

name属性带来的互斥效果

给多个details设置相同的name,它们就变成了同一组,同一时间只有一个能展开。我用一个简单的例子说明:

<details name="faq">
  <summary>第一项:为什么今天要加班?</summary>
  <p>因为项目马上要上线,大家需要冲刺一下。</p>
</details>

<details name="faq">
  <summary>第二项:加班有加班费吗?</summary>
  <p>有,按照公司规定发放。</p>
</details>

<details name="faq">
  <summary>第三项:明天能不能居家办公?</summary>
  <p>看项目进度,大概率可以。</p>
</details>

在支持的浏览器里,你打开第二项的时候,第一项会自动关闭。不需要加任何样式和JS。这个行为就像原生radio单选框一样,name相同的details是互斥的。

这个特性最适合做“常见问题”“操作步骤”“条款折叠”这种场景。用户再也不用一屏显示很多展开内容,交互也清晰。

不只是简单的手风琴:还能嵌套使用

name属性很灵活,它可以和不同的name组合成复杂的折叠结构。比如我做一个商品规格说明,一级分类用name=”group1″,二级分类用name=”group2″,这样两个级别互不影响。

来看看实际例子:

<details name="group1">
  <summary>尺寸信息</summary>
  <details name="group2">
    <summary>标准版</summary>
    <p>屏幕 6.1 英寸</p>
  </details>
  <details name="group2">
    <summary>Pro版</summary>
    <p>屏幕 6.7 英寸</p>
  </details>
</details>

这样可以实现:外层是手风琴,同一时间只能展示一个尺寸信息;内层也是手风琴,同一个尺寸下只能看一个版本。互不干扰,状态非常清晰。

这样的组件要是用JS写,需要判断点击的是哪个层,还要防止事件冒泡,麻烦得很。现在两个name就把分层逻辑表达得清清楚楚。

基础用法之外,怎么优雅地加动画

原生的details展开和收起是没有过渡动画的,直接弹出来。如果想加一点渐变效果,其实也不用写多少JS,只需要在CSS里配合interpolate-size或者calc-size(),但这个写法比较复杂。我一般只给summary加一个“三角箭头”的旋转,就能提升不少观感。因为本文不能使用style标签,我就不展示CSS了,你可以在自己的CSS文件里这样写:

summary::-webkit-details-marker { display: none; }
summary::before { content: '▶'; }
details[open] summary::before { content: '▼'; }

这属于锦上添花,不是核心。即便不加任何样式,浏览器默认的展开折叠也能用,不影响功能。

浏览器兼容性及降级

这个特性目前主要支持Chromium内核浏览器、Edge、以及Safari 17.2+。Firefox虽然支持details很久了,但对name属性的支持还在开发中。所以如果你的项目要兼容Firefox,最好加一个判断,或者使用Polyfill。

最简单的降级方式是:用原生details本身也能用,只是多个details不再互斥,用户依然可以自由开合。也就是说,没有name属性并不会让页面坏掉,只是手风琴效果缺失。这反而是很好的“渐进增强”思路:基础体验用HTML原生能力,增强体验靠新特性。

如果你的产品必须支持Firefox并且需要手风琴,可以临时给不支持name的浏览器加一小段JS监听toggle事件。大概长这样:

if (!('name' in HTMLDetailsElement.prototype)) {
  document.querySelectorAll('details[name]').forEach(details => {
    details.addEventListener('toggle', function() {
      const name = this.name;
      document.querySelectorAll(`details[name="${name}"]`).forEach(other => {
        if (other !== this) other.open = false;
      });
    });
  });
}

这只是一段备用方案,等Firefox更新后就彻底删掉。有了原生name属性,90%的代码其实只需要写上面这几行,而且以后还能移除。

实际项目里我用它替换了组件库的手风琴

上个月维护一个后台系统的“设置帮助”页面,里面原本用的是某个UI框架里的Collapse组件,光依赖体积就不少。我把它换成了几组带name的<details>,然后删掉了相关组件的CSS和JS。最后发到测试环境,产品经理说“跟以前一样没毛病”。

有几个同事问我怎么没引JS?我说这就是浏览器自带的功能。他们一开始还不信,直到打开开发者工具看了元素,才反应过来。

如果你也受够了折叠面板动不动就一堆代码,试试这个新特性吧。它让HTML又重新有了“写起来很爽”的感觉。

几个值得注意的小细节

1. name值不要重复得太离谱。它是按“同一根节点下”互斥的,本质上好像和radio一样,但具体细节更复杂。实测中,如果两个details嵌套在不同的父级里,只要name相同,也可能会互相影响;所以如果想控制互斥范围,最好给不同区域用不同的name,像前面的group1、group2那样。

2. 通过JavaScript动态创建的details同样支持name互斥,只要插入到DOM后,浏览器会自动处理状态。不需要rescan。

3. 对于不支持name的浏览器,如果使用了上面的降级JS,可能要注意避免频繁触发。在toggle事件里修改open属性会触发其他details的toggle事件,所以要做好防循环。我的办法是加一个变量标记正在执行,就很简单。

4. 如果你想刚打开页面时就默认显示某一项,可以在<details>上加open属性。浏览器会把它当作默认展开项,但在手风琴组里如果某一项默认展开,其他项再操作就会正常互斥。注意默认展开项只能有一个,如果有多个的话,后出现的会先被浏览器关掉。

总结

HTML的details name属性是我最近觉得最舒服的一个原生增强。它用最简单的方式实现了原来需要几十行JS的交互。当然,它做不到那种复杂的“手风琴带禁用状态”,但很多时候我们要的只是“别让我全都展开”而已。

以后当你再看到手风琴组件,先别急着找组件库,也许一个<details name="...>就够了。

用HTML的details name属性实现互斥手风琴,连JS都不用写
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 html 用HTML的details name属性实现互斥手风琴,连JS都不用写 https://www.taomawang.com/web/html/2651.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务