GJ 站长论坛・社区规则,请知晓

HTML 的 details 和 summary 元素做折叠面板。

liulian
liulian 初级会员超兽战士
发布于 2026-10-09 21:52 ·4 浏览 ·0 回复

<details> 和 <summary> 是浏览器原生的折叠面板:不写一行 JavaScript 就能展开收起,键盘操作、屏幕阅读器播报、页内查找自动展开全部开箱可用,2024 年后还能用 name 属性做互斥手风琴。
TL;DR:用 details+summary 做折叠;open 控默认态;name 做互斥手风琴;CSS 去箭头、JS 量高度做动画;summary 不加 role/tabindex。

details 和 summary 的最小可用写法是什么

结论:<details> 是折叠容器,<summary> 是那行常驻可见的标题,其余兄弟节点就是被折叠的内容;默认收起,加 open 布尔属性默认展开。

<details>
  <summary>运费怎么算?</summary>
  <p>满 99 包邮,其余地区 8 元。</p>
</details>

<details open>
  <summary>默认展开的项</summary>
  <p>给 details 加 open 属性即可。</p>
</details>

三条硬规则必须记住:

  1. summary 必须是 details 的第一个子元素。放第二位,浏览器会自造一个默认标题(中文环境显示"详细信息",英文显示"Details"),你写的那个 <summary> 会被当成普通文字排在下面,页面上出现两行标题。
  2. 一个 details 只认第一个 summary,第二个及之后的 summary 都不具备折叠触发能力。
  3. summary 天生可聚焦,Enter / Space 都能切换,不需要加 tabindex="0",也不需要写 click 事件去改 open。

details 的浏览器支持和能力差异怎么样

结论:基础的折叠能力和 toggle 事件三大浏览器都全绿,可以放心上生产;name 互斥手风琴、beforetoggle 事件、::details-content 属于渐进增强,用之前必须做特性检测。

特性ChromeFirefoxSafari
<details> / <summary> 基础折叠✅✅✅
open 属性✅✅✅
toggle 事件✅✅✅
name 互斥手风琴✅ 120+✅ 130+✅ 17.2+
beforetoggle 事件✅ 122+⚠️ 以 MDN 为准⚠️ 以 MDN 为准
::details-content 伪元素✅ 131+❌❌
summary::-webkit-details-marker✅❌✅

核查日期:2025-01;表中版本号请以 MDN 最新兼容性数据为准,标 ⚠️ 的行上线前需自行核实。

读表结论:基础折叠零成本落地;要兼容旧浏览器时,name 和 beforetoggle 只能当锦上添花,别把核心交互挂在上面。

name 属性怎么做互斥手风琴

结论:给同一组 details 相同的 name,浏览器会自动保证同组最多只展开一个,纯 HTML,零 JS,也不需要监听 toggle 再去手动关别人。

<details name="faq">
  <summary>问题一</summary>
  <p>答案一</p>
</details>
<details name="faq">
  <summary>问题二</summary>
  <p>答案二</p>
</details>

四个容易踩的点:

  • 互斥范围是整个文档树内同名元素,所以页面上有两组手风琴时,必须用 name="faq-product" / name="faq-price" 这种不同名字,否则会出现跨组联动。
  • 同名组里可以全部收起,点已经展开的那一项就能关掉,不是"必须留一个开着"。
  • 如果初始 HTML 里有多个同名 details 都带 open,浏览器只会保留第一个展开,其余强制关闭。
  • name 不影响 open 属性本身,程序里给多个同名项同时设 open = true,最终也只会有一个生效。

怎么去掉 summary 默认的三角箭头

结论:不同浏览器用了两套不同的伪元素,稳妥做法是两条规则都写,一次覆盖新旧内核。

summary {
  list-style: none;               /* 基础写法 */
  cursor: pointer;
}
summary::marker { content: ""; }              /* Firefox、Chrome 新版、Safari 新版 */
summary::-webkit-details-marker { display: none; } /* 旧 Chrome / 旧 Safari */

自定义图标推荐用 ::after 配 details[open] 切换:

summary { display: flex; justify-content: space-between; }
summary::after { content: "+"; }
details[open] summary::after { content: "−"; }

注意:三角标记属于纯装饰,去掉它不影响屏幕阅读器播报展开/收起状态。但不要图省事给 summary 设 display: none 再另做一版标题——那会把可聚焦能力和无障碍语义一起干掉。

details 展开动画怎么做

结论:height: auto 不是可插值长度,无法参与 CSS transition,所以动画要么用 JS 量出 scrollHeight 过渡到具体像素,要么用 Chrome 的 ::details-content + interpolate-size 新方案。

方案 A(兼容性最好,思路类似 Vue 的 <transition>):

const d = document.querySelector('details');
const box = d.querySelector('.content');

d.addEventListener('toggle', () => {
  if (d.open) return;                    // 展开时不做处理,直接切换
  // 收起前先锁高,再过渡到 0
  box.style.height = box.scrollHeight + 'px';
  requestAnimationFrame(() => {
    box.style.height = '0px';
  });
});

核心是三步:先给内容容器设显式像素高度 → 强制重排 → 再改高度触发过渡;展开时反过来,过渡结束把 height 设回 auto。

方案 B(现代 CSS,支持面窄):

:root { interpolate-size: allow-keywords; }

details::details-content {
  block-size: 0;
  transition: block-size .3s, content-visibility .3s;
  transition-behavior: allow-discrete;
}
details[open]::details-content { block-size: auto; }

transition-behavior: allow-discrete 是必须的,因为折叠内容的显示状态是离散切换的,不加这一行动画会直接跳变。方案 B 仅 Chrome 131+ 支持,用之前先判断 CSS.supports('interpolate-size', 'allow-keywords')。

怎么监听 details 的展开和收起

结论:用 toggle 事件,它在 open 状态变化之后触发;用 beforetoggle 则是在变化之前触发,可以 preventDefault() 拦下。

document.querySelectorAll('details').forEach(d => {
  d.addEventListener('toggle', () => {
    console.log('当前状态:', d.open ? '展开' : '收起');
  });
});

三个关键细节:

  1. toggle 事件不冒泡。想用事件委托,只能在 document 上以捕获阶段监听:document.addEventListener('toggle', handler, true)。
  2. 事件对象上有 newState(值为 "open" / "closed"),但保险起见直接读 details.open,兼容面更宽。
  3. 程序里改 details.open = true 同样会触发 toggle,初始化时要防重入,否则容易和业务状态打架形成死循环。

summary 里能放 h3 吗?可访问性要注意什么

结论:能放。HTML 规范允许 summary 的内容为 phrasing content 或一个标题元素,放 <h3> 还能帮屏幕阅读器和 SEO 建立文档大纲;但绝不能给它加 role="button"、aria-expanded 或 tabindex。

原因是浏览器已经在无障碍树里把 summary 暴露成了"可展开的摘要三角",并且自动维护展开状态。你手写 aria-expanded="false" 和它自己维护的状态会同时被读屏播报,出现"已折叠 已展开"这种重复朗读,而且手写的静态值不会随点击更新,反而制造错误信息。同理,tabindex="0" 会让它多出一次 Tab 停留点。

另外两点:summary 里不要放 <button> 或 <a> 这类可交互元素——点击时事件目标会变成内层元素,和折叠行为相互干扰;要放链接就放到展开后的内容区里。嵌套 details 时,子 details 必须放在父级展开内容中,不能塞进 summary 内。

React / Vue 里用 details 有哪些坑

结论:不要把 open 做成完全受控属性去"接管"浏览器行为,让浏览器自己管状态、框架只旁听结果,是最省事也最稳的做法。

版权声明:本文来自 GJ站长论坛《HTML 的 details 和 summary 元素做折叠面板。》
原文链接:https://www.gj0.com/thread-1257.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~