纯前端实现可访问的步骤条与向导:分步校验、返回不丢数据与 URL 同步
向导式表单(注册流程、下单流程、配置向导)几乎每个后台系统都有。真正难的地方在状态:用户点「上一步」回去改一个字段,之前填的不能丢;用户在第三步刷新页面,不能被打回第一步;用户想直接跳到某一步,得能跳但也要拦住未完成的必填项。
一、先把「步骤」建模成数据
不要把当前步骤存成一个数字就算了。至少要维护三份信息:步骤清单(顺序、标题、id)、当前步骤 id、以及每一步的数据与完成状态。
const steps = [
{ id: 'account', title: '账号信息' },
{ id: 'profile', title: '个人资料' },
{ id: 'confirm', title: '确认提交' },
];
const state = {
current: 'account',
data: { account: {}, profile: {} },
visited: new Set(['account']),
};
用 id 而不是下标做索引,步骤顺序调整时不会把状态搞乱。visited 用来决定哪一步的标题可点——只让用户点「已经到过的」和「当前步」,比全放开安全得多。
二、语义结构
步骤条本身是一个有序列表,用 ol 或 role="list",当前步用 aria-current="step"。已完成、进行中、未开始不要只靠颜色区分,要在标题文本或无障碍标签里体现。如果步骤条只是展示,不要给它 tabindex;如果可点击跳转,就做成真正的按钮并给出「第 2 步:个人资料」这样的可访问名。
主体区域每次切步骤后要把焦点移到该步的标题(h2 加 tabindex="-1"),否则键盘用户切完步骤还停在页脚的按钮上。步骤切换用 aria-live 播报「第 2 步,共 3 步」。
三、校验的时机
常见的错误做法是「下一步时校验全部字段」。更合理的是:进入某一步时校验该步已填字段(不阻塞),离开该步时校验该步必填字段(阻塞),提交时全量校验。
校验失败时把焦点移到第一个错误字段,并把错误摘要放在步骤标题下方用 role="alert" 播报;错误信息要挂在字段上(aria-describedby),不要只飘在顶部。
async function goNext() {
const errors = validateStep(state.current, state.data);
if (errors.length) {
focusFirstError(errors);
announce(`第 ${stepIndex + 1} 步有 ${errors.length} 个问题需要修改`);
return;
}
state.visited.add(nextId);
state.current = nextId;
}
四、返回不丢数据
关键在于「数据与步骤分离」:所有输入实时写进 state.data,而不是等提交时再收集。上一步只是换 current,DOM 重建时从 state.data 回填即可。
易踩的坑:动态字段(比如选了「公司」才出现的税号)在回去时因为条件不满足被卸载,值也一起丢了。做法是把值留在 state.data 里,只在渲染时决定是否显示,不要因为隐藏就删除。
另外注意 uncontrolled 与 controlled 混用。受控组件从 state 回填最稳;如果为了性能用了非受控,切换步骤前必须显式读一次 DOM 值存回 state。
五、URL 同步与刷新恢复
把当前步骤写进 URL(query 或 hash),刷新就能回到原处,也方便分享和回退。回退键(浏览器 back)应该等价于「上一步」,这要求步骤切换时用 pushState 而不是 replaceState。
const syncUrl = (id) => history.pushState({ step: id }, '', `?step=${id}`);
window.addEventListener('popstate', (event) => {
state.current = event.state?.step || steps[0].id;
render();
});
数据是否也进 URL 要看场景:短表单可以把非敏感字段序列化进去,长表单或含敏感信息的不要进 URL,改用 sessionStorage,并在提交成功后清理。刷新恢复时要处理「URL 里的步骤与本地数据不匹配」——比如数据已过期,此时应退回到第一个不完整的步骤并提示,而不是直接渲染空表单。
六、提交与幂等
提交按钮在请求期间必须禁用并显示进行中状态,避免重复提交。更稳的做法是给每次提发生成一个幂等键(放在 state 里,重试时复用),后端据此去重。提交失败不要把用户打回第一步,停在确认页并保留数据,给出重试入口。
七、离开保护
填了一半要关页面,得拦一下。用 beforeunload 只能在刷新/关闭时提示,前端路由跳转要在路由守卫里拦。注意别滥用:表单没被修改过就不要弹,否则用户会很快学会忽略所有弹窗。
八、测试清单
覆盖:上一步回来字段值还在、动态字段回去不丢、跳步骤时的拦截、刷新恢复到原步骤、back 键等价上一步、校验失败焦点落点与播报、错误摘要与字段错误对应、提交中重复点击、提交失败后数据保留、离开保护只在脏数据时触发、以及键盘走完整流程。用读屏完整走一遍三步流程,最能暴露「切步骤不播报」这类问题。
九、小结
向导的可靠性来自三件事:数据与步骤分离、校验按时机分层、步骤状态可持久化并可从 URL 恢复。把这三件事做实,再加动态字段、离开保护和幂等提交,多步流程就不再是 bug 高发区。
- 点赞
- 收藏
- 关注作者
评论(0)