原生 <details> 不适合多条日志独立展开,因其无状态持久化、无键盘支持(需 tabindex 和 role="button")、<summary> ARIA 支持弱、无视觉反馈;须配合 JS 用 localStorage 记录状态、toggle 事件监听及语义化 HTML 结构(如 <ol reversed>、<li> 包裹、aria-labelledby)方可实现可访问、可记忆、可检索的更新日志。

<details> 标签本身不支持嵌套展开状态持久化,也不响应 click 事件冒泡外的交互(比如回车触发),直接用它做更新日志容易出现「点开一个,其他自动收起」「刷新后全部闭合」「键盘用户无法操作」等问题。得配合少量 JS 和语义化结构才能真正友好。
为什么原生 <details> 不适合多条日志独立展开
浏览器对多个 <details> 的默认行为是各自独立——这点其实 OK;但问题出在:没有视觉反馈告诉用户哪条已展开、无法记忆上次状态、<summary> 不支持 role="button" 以外的 ARIA 属性,导致屏幕阅读器识别率低。
实操建议:
- 每条日志必须用独立的
<details>包裹,不能共用一个 - 给
<summary>显式添加tabindex="0"和role="button",否则键盘Enter/Space无法触发 - 避免在
<summary>里放<h3>或<p>—— 会破坏可访问性层级,改用<span>+ CSS 控制样式 - 不要依赖
open属性硬编码展开状态,它无法跨页面保留
用 localStorage 记住用户展开偏好
用户点开 v2.3.1 的详情,下次进来还希望它是开的。原生 <details> 不提供钩子,得监听 toggle 事件手动存取。
立即学习“前端免费学习笔记(深入)”;
示例逻辑:
document.querySelectorAll('details').forEach(details => {
const key = `changelog-${details.dataset.version}`;
if (localStorage.getItem(key) === 'true') details.open = true;
details.addEventListener('toggle', () => {
localStorage.setItem(key, details.open.toString());
});
});
注意点:
- 必须用
dataset.version这类自定义属性标识每条日志,不能靠索引(顺序可能变) -
toggle事件在 Safari 中兼容性良好,但旧版 iOS Safari 需要 polyfill,建议检查supports再绑定 - 如果日志量大(>50 条),批量读写
localStorage可能卡顿,考虑节流或改用sessionStorage
让时间线和版本号具备语义与可检索性
纯视觉排版(比如用 <hr> 分隔)对搜索引擎和辅助技术不友好。更新日志本质是「按时间倒序排列的带版本标识的变更列表」,应利用 HTML5 结构化标签强化含义。
推荐结构:
<section aria-labelledby="log-title">
<h2 id="log-title">更新日志</h2>
<ol reversed>
<li>
<details data-version="2.3.1">
<summary tabindex="0" role="button">
<span>v2.3.1(2024-06-15)</span>
</summary>
<ul><li>修复导出 CSV 时中文乱码</li></ul>
</details>
</li>
</ol>
</section>
关键点:
-
<ol reversed>告诉浏览器这是倒序列表,比纯<div>更利于爬虫理解时序 - 每个
<details>外包一层<li>,保持列表完整性 -
aria-labelledby关联标题,避免屏幕阅读器重复读「details」二字 - 版本号和日期放在
<summary>内同一行,方便快捷键跳转时一并获取
最常被忽略的是键盘焦点管理:用户用 Tab 进入 <summary> 后,按 Enter 应该展开/收起,但默认只在 Chrome/Firefox 生效,Safari 需要额外 preventDefault 并手动切换 open 属性。这个细节没处理好,等于把键盘用户挡在门外。



















