滚动加载时新内容需用aria-live="polite"作用于role="feed"容器并配合aria-atomic="false",搭配加载按钮、intersectionobserver焦点管理及aria-busy等属性确保屏幕阅读器正确感知。

滚动加载时新内容如何被屏幕阅读器感知
自动插入新内容后,屏幕阅读器默认不会朗读——它只响应显式触发的 DOM 变化通知。靠 aria-live 是最直接的解法,但必须用对区域和策略。
在列表容器上加 aria-live="polite" 和 aria-atomic="false",确保新条目插入后被朗读,又不打断当前语音流。别用 aria-live="assertive",它会强行中断用户操作,尤其在连续加载时体验极差。
常见错误是把 aria-live 放在“加载中”提示文字上,结果只读“加载中”,新内容反而沉默。正确位置是包裹整个列表的容器(如 <div role="feed">),且该容器不能被频繁替换或重写 innerHTML。<ul>
<li>每次插入新项前,先清空旧的 <code>aria-busy="true" 状态(如有)
aria-live 区域的 DOM 变更,比如用 el.textContent = el.textContent 强制刷新(轻量且兼容)DOMPurify.sanitize() 过滤,否则 script 标签可能执行并破坏 aria-live 行为加载更多按钮为什么不能省,以及怎么绑定焦点
纯滚动触底自动加载对键盘和屏幕阅读器用户是“不可见操作”。没有显式按钮,他们就卡在最后一条,完全不知道还有内容、也无法主动触发加载。
必须提供一个始终可聚焦的 <button>加载更多文章</button>,放在列表末尾,且不随滚动隐藏。它不是备选方案,而是可访问性刚需。
- 按钮需有明确
role="button"(原生<button></button>已自带,无需额外加) - 加载中时设
aria-busy="true"和aria-disabled="true",同时保持tabindex="0"(禁用状态仍需可聚焦,否则键盘用户无法到达) - 加载完成、新内容插入后,如果用户当前焦点在按钮或其父
role="feed"内,则调用newFirstItem.querySelector('h2').focus()把焦点移到新内容第一项标题上 - 不要在按钮上加
tabindex="-1"——那会让它彻底退出 Tab 流,违背存在意义
用 IntersectionObserver 触发加载时怎么保焦点不丢
用 IntersectionObserver 监听底部占位元素比监听 window.onscroll 更稳,但它本身不处理焦点逻辑。焦点丢失通常发生在两个时刻:占位元素被 unobserve 后 DOM 更新、新内容插入后未及时接管焦点。
关键不是“阻止焦点移动”,而是“在合适时机主动归位”。观察回调里不能直接 focus(),因为此时新 DOM 还没挂载完。
- 在
observer.observe(triggerEl)前,先记录当前document.activeElement是否在 feed 区域内(用feedEl.contains(activeEl)判断) - fetch 成功后,在
then里插入新节点,再用requestAnimationFrame(() => { /* 焦点逻辑 */ })确保 DOM 已渲染 - 只在满足“焦点原在 feed 内”+“新内容非空”两个条件时才执行聚焦,避免打断表单输入等场景
- 若用户正用方向键浏览列表,聚焦新首项后应同步更新内部索引,否则下一次 ArrowDown 会跳过刚插入的内容
role="feed" 容器里哪些属性容易漏配
role="feed" 不是加了就完事。WAI-ARIA 规范要求它必须配合特定行为,否则屏幕阅读器可能忽略或误读。
最常漏的是 aria-busy 和 aria-labelledby。前者用于声明整个 feed 正在动态更新,后者让屏幕阅读器把标题和 feed 关联起来,避免朗读成“一个无名 feed”。
-
aria-busy="true"必须在 fetch 开始时设,finally中设回false;漏掉会导致后续所有更新都不被感知 -
aria-labelledby="feed-title"要指向页面中真实存在的<h2 id="feed-title"></h2>,不能是随便写的 ID - 容器内每个条目必须有明确角色,如
role="article"或role="listitem",且带aria-labelledby指向其标题,否则屏幕阅读器无法建立结构关系 - 别给
role="feed"容器设tabindex="-1"——它不是要程序化聚焦的目标,而是语义容器;聚焦逻辑应落在具体条目或按钮上
实际中最容易被忽略的,是“焦点是否还在 feed 内”这个判断条件。很多人写了 focus() 却没包一层 if (feedEl.contains(document.activeElement)),结果用户正在编辑搜索框,页面突然把焦点拽走,体验直接崩掉。











