原生 details/summary 标签最快实现可折叠列表,无需 JS 即支持点击、键盘操作及 ARIA;应优先使用语义化标签而非手写事件监听,兼容主流现代浏览器。

用 details 和 summary 最快实现可折叠列表
不用写 JS 就能完成基础展开/收起,现代浏览器原生支持。关键不是“怎么监听 toggle”,而是“该不该自己写事件”——90% 场景直接用语义化标签更稳。
HTML 结构只需这样:
<details>
<summary>点击展开列表</summary>
<ul>
<li>选项一</li>
<li>选项二</li>
</ul>
</details>
浏览器自动处理点击、键盘(空格/回车)、焦点和 ARIA 属性。不需要 addEventListener('toggle'),因为 toggle 事件只在 details 元素上触发,且兼容性已覆盖 Chrome 12+、Firefox 49+、Safari 6+、Edge 79+。
- 不推荐给
summary单独加click监听——会干扰原生行为,导致双击才生效或键盘不可用 - 若需自定义样式,重点改
summary::marker和details[open] > summary状态 - 服务端渲染时注意:SSR 中
details默认不展开,open属性必须显式写出才能初始展开
手动实现时,别用 click 判断开闭状态
如果必须手写(比如要兼容 IE 或嵌入复杂组件),核心陷阱是:靠 class 或布尔变量判断“当前是否展开”,容易和用户真实交互脱节。正确做法是监听 toggle 事件并读取 details.open。
立即学习“前端免费学习笔记(深入)”;
示例代码:
const el = document.querySelector('details');
el.addEventListener('toggle', () => {
if (el.open) {
console.log('已展开');
} else {
console.log('已收起');
}
});
-
toggle事件在用户交互(鼠标、键盘)后触发,但不会在 JS 直接赋值el.open = true时触发——这是设计使然,别试图 patch - 不要用
el.classList.contains('open')判断状态,而应始终读el.open属性,它永远反映真实 DOM 状态 - 避免在
toggle回调里再调用el.open = !el.open,会造成死循环
需要动画时,CSS 过渡不能直接作用于 height
details 内容默认是 display: block,但展开收起是靠 display 切换(非 height 变化),所以直接写 transition: height 0.3s 没用。
- 可行方案:用
max-height+ 固定像素值(如max-height: 500px),配合overflow: hidden - 更健壮的做法:用
transform: scaleY()+origin,对性能更友好,且无需预估高度 - JS 配合动画时,应在
toggle后用getComputedStyle(el).height获取滚动高度,再设max-height——但要注意,此时元素尚未重排,需用requestAnimationFrame延迟读取
移动端触摸穿透和 focus 管理容易被忽略
在 iOS Safari 或某些安卓 WebView 中,summary 点击可能触发两次,或焦点丢失导致键盘操作失效。
- 确保
summary是可聚焦元素(默认是,但若加了tabindex="-1"就会破坏) - 避免在
summary上绑定touchstart或touchend,它们会干扰原生click流程 - 如果列表内有表单控件(如
input),收起时需手动 blur,否则软键盘可能残留 - 部分低版本 UC 或 QQ 浏览器不支持
details,检测可用typeof HTMLDetailsElement !== 'undefined',fallback 方案建议用aria-expanded+ 手动切换hidden属性
details。



















