【前端从0到1实战】第7篇:构建“多步骤表单向导” (Multi-Step Form)

【前端从0到1实战】第7篇:构建“多步骤表单向导” (Multi-Step Form)

欢迎来到本系列的第三篇。我们即将从“内容展示”组件(如 Tab 和手风琴)毕业,进入一个更高级、更具挑战的领域:“复杂交互与状态管理”。

“多步骤表单”是解决复杂信息收集(如注册流程、结账页面、问卷调查)的最佳方案。它通过将一个庞大的表单分解为多个小步骤,极大地提升了用户体验,减少了用户的心理压力。

本篇我们将手写一个功能完整的表单向导,它将包含:

一个动态的“进度条”。

平滑的“步骤切换”动画。

“上一步”/“下一步”/“提交”的逻辑切换。

第一部分:HTML 结构搭建 (骨架)

一个多步骤表单的结构比之前的组件都要复杂。我们需要:

进度条 (.progress-bar):用于显示当前进度。

表单步骤容器 (.form-steps-container):一个“视口”,它将隐藏所有非当前步骤的面板。

多个步骤面板 (.form-step):包含表单字段的实际内容。

导航按钮 (.form-navigation):用于前进和后退。





1

账户信息




2

个人资料




3

确认






<!--
步骤 1 (默认激活)



创建您的账户
















完善您的个人资料
















确认信息




请检查您的信息,然后提交。




邮箱: ...








第二部分:CSS 样式 (皮肤与动画)

CSS 的挑战有两个:

进度条样式:如何处理 .is-active(当前)和 .is-completed(已完成)两种状态。

面板切换动画:我们将使用 transform: translateX() 来实现平滑的“滑动”效果,而不是生硬的 display: none。

/* --- 基础容器样式 --- /
.form-container {
width: 650px;
max-width: 100%;
margin: 40px auto;
border: 1px solid #dfe4ea;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.07);
background-color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/
防止子元素溢出圆角 */
overflow: hidden;
}

/* --- 1. 进度条样式 --- */
.progress-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30px 40px;
background-color: #f8f9fa;
}

.progress-step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex-basis: 100px; /* 每个步骤的基础宽度 */
}

.progress-step-icon {
width: 35px;
height: 35px;
border-radius: 50%;
background-color: #dfe4ea;
color: #57606f;
border: 3px solid #dfe4ea;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
transition: all 0.3s ease;
}

.progress-step-label {
margin-top: 8px;
font-size: 14px;
font-weight: 500;
color: #57606f;
transition: all 0.3s ease;
}

.progress-line {
flex-grow: 1;
height: 3px;
background-color: #dfe4ea;
margin: 0 10px;
transition: all 0.3s ease;
}

/* 进度条激活状态 */
.progress-step.is-active .progress-step-icon {
background-color: #fff;
border-color: #007bff;
color: #007bff;
}
.progress-step.is-active .progress-step-label {
color: #007bff;
}

/* 进度条完成状态 */
.progress-step.is-completed .progress-step-icon {
background-color: #007bff;
border-color: #007bff;
color: #fff;
}
.progress-step.is-completed .progress-step-label {
color: #2f3542;
}
.progress-step.is-completed + .progress-line {
background-color: #007bff;
}

/* --- 2. 表单步骤样式 (核心动画) --- */

/* “视口”:固定高度,隐藏溢出 /
.form-viewport {
height: 280px; /
必须指定一个固定高度 */
overflow: hidden;
}

/* “步骤容器”:使用 Flex,宽度是 300% (因为它有3个步骤) /
.form-steps-container {
display: flex;
width: 300%; /
100% * 3 个步骤 */
transition: transform 0.4s cubic-bezier(0.77, 0, 0.175, 1);
}

/* 每个步骤面板,占满容器的 1/3 /
.form-step {
flex-basis: 33.333%;
padding: 20px 40px;
box-sizing: border-box; /
保证 padding 不会破坏 33.333% 的宽度 /
/
(在 CSS 中, .is-active 在这里只用于初始定位) */
}

.form-step-header {
margin-bottom: 25px;
}
.form-step-header h2 {
margin: 0;
color: #2f3542;
}

.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #dfe4ea;
border-radius: 5px;
box-sizing: border-box;
font-size: 1em;
}

/* --- 3. 导航按钮样式 --- */
.form-navigation {
display: flex;
justify-content: space-between;
padding: 25px 40px;
background-color: #f8f9fa;
border-top: 1px solid #dfe4ea;
}

.btn {
padding: 10px 25px;
font-size: 1em;
font-weight: 500;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary {
background-color: #007bff;
color: #fff;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #dfe4ea;
color: #57606f;
}

/* 按钮的禁用状态 */
.btn:disabled {
background-color: #e9ecef;
color: #adb5bd;
cursor: not-allowed;
}

第三部分:JS 交互逻辑 (状态管理)

这是本文最核心的部分。我们将引入一个状态变量 currentStep,所有的 DOM 更新都围绕这个变量展开。

document.addEventListener('DOMContentLoaded', () => {

// --- 1. DOM 元素获取 ---
// 按钮
const btnNext = document.getElementById('btn-next');
const btnPrev = document.getElementById('btn-prev');

// 步骤面板
const allSteps = document.querySelectorAll('.form-step');
const stepsContainer = document.querySelector('.form-steps-container');

// 进度条
const allProgressSteps = document.querySelectorAll('.progress-step');
const allProgressLines = document.querySelectorAll('.progress-line');

// --- 2. 状态管理 ---
let currentStep = 1; // 当前步骤 (从 1 开始)
const totalSteps = allSteps.length; // 总步骤数

// --- 3. 核心功能函数:更新表单状态 ---
// 这是一个“中央控制器”,所有状态变化都由它分发
const updateFormState = () => {

// --- (A) 更新步骤面板的“滑动” ---
// currentStep 是 1, 偏移 0%
// currentStep 是 2, 偏移 -33.333% (100 / 3)
// currentStep 是 3, 偏移 -66.666% (200 / 3)
const offsetPercentage = -((currentStep - 1) * (100 / totalSteps));
stepsContainer.style.transform = translateX(${offsetPercentage}%);

// --- (B) 更新进度条 ---
allProgressSteps.forEach((step, index) => {
const stepNum = index + 1;

if (stepNum === currentStep) {
// 标记为“当前”
step.classList.add('is-active');
step.classList.remove('is-completed');
} else if (stepNum < currentStep) {
// 标记为“已完成”
step.classList.add('is-completed');
step.classList.remove('is-active');
} else {
// 标记为“未开始”
step.classList.remove('is-active');
step.classList.remove('is-completed');
}
});

// --- (C) 更新导航按钮 ---
if (currentStep === 1) {
// 第一步:禁用“上一步”
btnPrev.disabled = true;
} else {
btnPrev.disabled = false;
}

if (currentStep === totalSteps) {
// 最后一步:将“下一步”变为“提交”
btnNext.textContent = '提交';
} else {
btnNext.textContent = '下一步';
}
};

// --- 4. 事件绑定 ---

// “下一步”按钮点击
btnNext.addEventListener('click', () => {
if (currentStep < totalSteps) {
// 增加步骤
currentStep++;
// 更新 UI
updateFormState();
} else {
// 已经是最后一步 (“提交”按钮)
alert('表单已提交!');
// (在这里可以添加真正的表单提交
// 例如:document.getElementById("multi-step-form").submit()
// 或使用 fetch API)
}
});

// “上一步”按钮点击
btnPrev.addEventListener('click', () => {
if (currentStep > 1) {
// 减少步骤
currentStep--;
// 更新 UI
updateFormState();
}
});

// --- 5. 初始化 ---
// 页面加载时,立即执行一次,以确保 UI 处于正确的第一步状态
updateFormState();

});

总结

恭喜!我们完成了一个复杂的、带状态的组件。

我们学到了:

HTML: 如何为“状态驱动”的 UI 搭建骨架(进度条、视口、面板、按钮)。

CSS:

如何使用 transform: translateX() 结合 overflow: hidden 来实现平滑的滑动动画。

如何为进度条定义 .is-active 和 .is-completed 两种不同的激活状态。

JS (核心):

如何使用一个状态变量 (currentStep) 来驱动所有 UI 变化。

如何编写一个中央更新函数 (updateFormState),将“状态”同步到 DOM,这是解耦的
关键。

在下一篇文章中,我们将继续探索动画,挑战“轮播图 (Carousel)”组件。

posted @ 2025-11-17 15:51  GreenBoos2025  阅读(114)  评论(0)    收藏  举报