fieldset是唯一能批量控制验证和禁用的原生方案,必须作为form直系子元素并配legend;“下一步”按钮须设type="button";required需动态增删;表单状态须用sessionStorage按步持久化。

fieldset 是唯一能批量控制验证和禁用的原生方案
不用 fieldset 就没法真正隔离每步的验证范围,也做不到一键禁用整组字段。很多人用 div 包裹步骤,结果发现 form.checkValidity() 仍校验所有 input,哪怕它们 display: none。这是因为浏览器只认 fieldset[disabled] 的语义禁用——它会自动让子控件不可聚焦、不参与校验、不出现在 FormData 中。
必须确保:
-
fieldset是form的直系子元素,不能套在div或其他容器里 - 每个
fieldset必须有legend,否则语义失效,屏幕阅读器无法播报步骤标题 - 切换步骤时,对非当前
fieldset设置disabled = true,而不是仅靠 CSS 隐藏 - 别给
fieldset加required——这个属性对fieldset无效,且会干扰校验逻辑
“下一步”按钮必须是 type="button"
这是最常被忽略的触发点。默认 <button></button> 是 type="submit",点“下一步”会触发表单级提交,导致未填写字段报错、页面刷新或跳转。哪怕你写了 event.preventDefault(),也挡不住原生校验气泡弹出和焦点混乱。
正确做法只有两个字:type="button"。它彻底切断默认行为,把控制权完全交给 JS。
立即学习“前端免费学习笔记(深入)”;
还要注意:
- 不要依赖
onclick写内联 JS,统一用addEventListener绑定,方便复用和测试 - 点击后先调用
validateStep(current),只校验当前fieldset.active input,通过再切换 - 切换前手动移除上一步的
aria-current,给当前fieldset加上aria-current="step"
required 属性必须动态开关,不能靠 CSS 隐藏绕过
浏览器根本不理 display: none 或 visibility: hidden,只要 input 在 form 里且带 required,form.checkValidity() 就会把它算进去。用户还没看到第 2 步,就因第 1 步隐藏字段报错。
解决方案不是弃用 required,而是精准控制它的存在:
- 切到第
n步前,清除所有非当前步input[required]:document.querySelectorAll('input[required]').forEach(el => el.removeAttribute('required')) - 再给当前步所有可编辑字段加回:
document.querySelectorAll('fieldset.active input:not([readonly]):not([disabled])').forEach(el => el.setAttribute('required', '')) - 如果某步字段是条件显示(比如勾选“需要发票”才出现地址栏),也要同步 toggle
required,不能只靠显隐
表单状态必须持久化,否则刷新就丢数据
用户填完第 2 步,刷新页面,直接回到第 1 步——这不是体验差,是功能残缺。不能只靠 JS 变量存状态,得落地到 sessionStorage 或 localStorage。
但直接 JSON.stringify(form.elements) 会出问题:
- 未修改字段(如
input.defaultValue === input.value)不该存,避免覆盖真实值或存入空字符串 - 敏感字段(如密码)必须过滤,
name包含password或pwd的一律跳过 - 键名按步区分:
form_step_1、form_step_2,别全塞进一个 key 里 - 恢复时先判断
sessionStorage.getItem('current_step'),再逐个还原对应字段的value,最后调用goToStep(n)
最易被忽略的是:切换步骤时没保存当前步的验证状态,导致用户点“下一步”才突然弹出一堆红框,却不知道该从哪改起。应该在离开前就跑一遍 validateStep(current),把错误信息缓存下来,下次进入时直接标红对应字段。



















