
怎么用 aria-busy 告诉屏幕阅读器“正在加载”
不能只靠视觉判断加载状态——对使用读屏软件的用户来说,aria-busy="true" 是唯一能明确传达“内容暂不可用、别急着读”的语义信号。它不触发任何样式或行为,纯属无障碍声明。
常见错误现象:aria-busy 加在容器上,但新内容插入后没及时设回 false;或者加在按钮上(无效),其实必须加在**正在被动态更新的容器元素**上(比如 <div id="list">)。
aria-busy="true" 必须配合 role="region" 或已有语义容器(如 main、section)使用,否则多数读屏器忽略设为 true 后,读屏器会暂停对该区域的自动朗读(包括焦点进入时的默认播报)加载完成务必手动设回 aria-busy="false",否则该区域永久“失声”不要和 aria-live 混用——aria-live 是通知变化,aria-busy 是冻结通知,二者逻辑冲突
无限滚动容器为什么必须设 role="feed" 或 role="list"
单纯用 div + overflow-y: auto 实现滚动,对读屏用户等于“一堵墙”。浏览器不会主动把新增列表项当作可导航结构,焦点也无法用方向键逐条移动。
使用场景:新闻流、评论区、商品瀑布流等持续追加内容的区域。
立即学习“前端免费学习笔记(深入)”;
优先用 role="feed"(HTML 5.2+ 推荐),它语义上就表示“时间序内容流”,读屏器支持原生导航(如 NVDA 的 F 键跳到下一条)若需兼容老版本读屏器(如 JAWS 2019 及更早),改用 role="list" + 每个子项加 role="listitem"避免用 role="scrollable"(非标准属性,无读屏支持)容器必须有明确的 id,且首次加载时就存在 DOM 中,不能等 JS 创建完才挂载
IntersectionObserver 触发加载时,怎么避免重复请求和错位渲染
无限滚动的核心是监听底部元素是否进入视口,但 IntersectionObserver 的回调时机和阈值设置不当,会导致“刚加载完又触发一次”或“滚动过快时漏掉触发”。
参数差异:threshold 设为 0(默认)时,只要 1px 进入就触发,容易抖动;设为 1 则必须完全可见才触发,可能卡住。
推荐 threshold: [0, 0.1, 0.5] —— 多级触发,提前预加载,避免白屏每次触发后立刻调用 observer.unobserve(target),防止同一元素多次回调加载中状态要同步控制:设 aria-busy="true" + 禁用滚动监听(用布尔标志位),避免快速滚动时并发请求新内容插入后,用 scrollIntoView({ block: ‘nearest’ }) 对齐旧末尾,否则滚动位置会跳变
为什么 tabindex="-1" 要加在每条新加载的列表项上
读屏用户常用 Tab 键线性浏览内容,但无限滚动中新插入的项默认不可聚焦。不加 tabindex="-1",这些项就彻底“隐身”于键盘导航路径之外。
容易踩的坑:只给首条加,或加在容器上(无效);更糟的是加 tabindex="0",导致 Tab 键顺序混乱、焦点跳转失控。
每条新插入的项(如 <article> 或 <li>)都必须带 tabindex="-1"插入后立即执行 element.focus() 是反模式——会强制打断用户当前操作;正确做法是让用户自己按 Tab 到达如果该项本身含可交互元素(如按钮),则无需额外加 tabindex,由内部焦点管理即可配合 role="feed" 时,部分读屏器(如 VoiceOver)会自动将新项纳入“下一条”导航,但仍建议保留 tabindex="-1" 保底
最易被忽略的是:aria-busy 和 role 属性必须在 JS 插入新内容前就写死在容器上,而不是等加载完成再补——DOM 插入瞬间读屏器就开始解析语义,晚了就来不及。

评论(0)