滚动时屏幕阅读器丢失焦点的典型表现是长列表滚动后仍朗读顶部内容,因浏览器默认不触发 focus();应使用 aria-current="page" 标记可视区首项,配合 scroll 事件与 getBoundingClientRect() 计算可见项。

滚动时屏幕阅读器丢失焦点的典型表现
长列表(比如商品页、聊天记录)滚动后,屏幕阅读器常停留在原位置,不随视觉焦点移动——用户听到的仍是顶部内容,但实际看到的是底部。这不是 bug,是浏览器默认行为:滚动不触发 focus(),而屏幕阅读器只朗读当前获得焦点的元素或其最近的 aria-live 区域。
用 aria-current="page" 标记可视区域首项
比强行 focus 更可靠:告诉屏幕阅读器“当前正在看这一项”。它不依赖 DOM 焦点,而是靠语义标记驱动朗读。
- 监听
scroll事件,计算视口内第一个可见的列表项(用getBoundingClientRect()判断top >= 0 && top ) - 移除所有
aria-current,给该元素设aria-current="page" - 确保该项有明确的文本内容(如
<li>订单 #12345</li>),否则屏幕阅读器可能跳过 - 避免在
requestAnimationFrame外高频设置——滚动中每帧只更新一次,否则触发过多 ARIA 变更通知
scrollIntoView({ block: 'nearest', inline: 'nearest' }) 的陷阱
很多人想用它把目标项拉到视口并自动聚焦,但问题明显:
- 调用
element.scrollIntoView()后,element.focus()不一定成功——尤其当元素不可聚焦(没tabindex或被aria-hidden="true"隐藏) -
block: 'nearest'在部分 Safari 版本下失效,仍会滚动到顶部 - 连续滚动时频繁调用会导致视图抖动,干扰用户手动拖拽
- 真正需要的是“静默定位”,不是强制滚动——用
aria-current+ CSS:focus-within视觉反馈更稳妥
长列表必须加 role="list" 和每项 role="listitem"
纯 <ul><li> 在某些屏幕阅读器(如 NVDA + Firefox)中会被合并朗读为“列表,共 200 项”,跳过中间内容。显式声明角色能激活逐项导航模式。
立即学习“前端免费学习笔记(深入)”;
-
<ul role="list">替换原<ul>,即使语义正确也建议加——兼容性更稳 - 每个
<li>加role="listitem",尤其当它是动态渲染或用<div>模拟时 - 避免在
listitem内嵌套另一个role="list",否则屏幕阅读器可能陷入多层嵌套播报 - 若列表项含操作按钮,确保按钮有
aria-label(如<button aria-label="删除订单 #12345">),否则仅读“按钮”无上下文
最易被忽略的是:滚动锚定(scroll anchoring 开启时,aria-current 更新可能被浏览器延迟执行——需在 scrollend 事件而非 scroll 中处理,但 iOS Safari 不支持该事件,得回退到节流后的 scroll + setTimeout 判定静止。



















