用语义化 结构,css 伪元素绘制圆点与连线,js 通过 data-status 控制状态并校验点击跳转条件,配合 css 变量实现主题与响应式,分阶段重置避免闪屏。

怎么用纯 HTML + CSS 实现可交互的 step indicator
不能只靠 <progress></progress> 或 <meter></meter>,它们不支持自定义步骤标签和点击跳转。真正可用的方案是语义化结构 + CSS 伪元素 + 少量 JS 控制状态。
核心结构用 <ol></ol> 包裹 <li>,每个 <li> 代表一步,用 data-status="active"、"completed"、"pending" 控制视觉状态。CSS 用 ::before 绘制圆点,::after 绘制连接线,避免额外 DOM 节点。
常见错误:直接用 <div> 堆砌,导致无法键盘导航、屏幕阅读器不可读;或把连线写死成绝对定位,响应式时断裂。
<ul>
<li>每步 <code><li> 必须含 <span></span> 包裹文字,确保可聚焦和 ARIA 兼容
linear-gradient 或伪元素 ::after 更可控min-width: 24px),否则触控困难如何让步骤条支持点击回退且不破坏当前流程逻辑
点击某步触发跳转的前提是:这步已 completed,或当前处在可编辑的宽松模式。不能无条件允许跳转,否则表单数据会丢失或校验失效。
JS 层只需监听 <li> 的 click,检查 dataset.status,再调用业务层的 goToStep(index) 函数——这个函数由你控制是否重置后续步骤、是否保留输入值。
容易踩的坑:直接在 click 里 setAttribute('data-status', 'active'),但没同步更新表单域或清空后续验证态,导致 UI 和数据不同步。
- 只对
data-status="completed"或data-status="active"的项启用点击 - 禁用状态用
pointer-events: none; opacity: .5,别只靠disabled(<li>不支持该属性) - 跳转后记得
scrollIntoView({ behavior: 'smooth' })到对应步骤区域,尤其长表单
为什么用 CSS 变量控制颜色比写死 class 更可靠
当产品需要深色模式切换、主题换色、或 A/B 测试不同配色时,硬编码 .step--success { color: #28a745; } 会快速失控。CSS 变量能统一注入主题上下文,JS 也能动态改写 :root 下的值。
例如定义 --step-active-color: #007bff;、--step-line-color: #dee2e6;,所有步骤样式基于变量计算,连渐变连线都能写成 background: linear-gradient(to right, var(--step-active-color), var(--step-active-color))。
注意:Safari 对 CSS 变量在 linear-gradient 中的支持曾有 bug(旧版 iOS),若需兼容 iOS 13 以下,得用 JS 注入 style 标签 fallback。
- 变量名带命名空间,如
--step-color-active,避免和项目其他组件冲突 - 用
@supports (color: oklch(0% 0 0))做现代色彩空间降级,非必须但利于未来扩展 - 不要在 JS 里反复
getComputedStyle().getPropertyValue()读变量,缓存到对象里
表单提交后怎么平滑重置步骤条而不闪屏
提交成功后重置,不是简单 location.reload() 或清空所有 data-status。要保留第一步激活态,同时清除所有表单输入、验证提示、错误类——但不能让步骤圆点“跳一下”再回来。
关键在分阶段操作:先 removeAttribute('data-status') 所有 <li>,再给第一个设 data-status="active",最后用 requestAnimationFrame 批量更新表单控件值。这样浏览器只触发一次重排。
另一个陷阱:用 innerHTML = '' 清空表单区域,会销毁所有事件监听器和 Vue/React 绑定,纯静态页才安全。
- 重置前保存当前步骤索引,便于用户取消后恢复
- 如果用了
input的setCustomValidity,重置时必须调用setCustomValidity('')否则仍报错 - 动画过渡用
transition: all .2s ease在<ol></ol>上,别分散到每个<li>
最易被忽略的是异步步骤(比如某步要等接口返回才能进下一页),这种不能只靠前端状态控制,必须和服务端对齐 current_step 字段,否则刷新后断在错误位置。











