【前端从0到1实战】第1篇:构建一个Tab选项卡 (Tabbed Interface)
【前端从0到1实战】第1篇:构建一个Tab选项卡 (Tabbed Interface)
欢迎来到我们的实战系列。在几乎所有的 Web 应用(从个人中心到管理后台)中,Tab 选项卡都是最常见的 UI 组件之一。它允许我们在有限的空间内展示大量信息,而不会让界面显得杂乱。
本篇我们将从零开始,手写一个功能完备、样式精良的 Tab 选项卡组件。
第一部分:HTML 结构搭建 (骨架)
一个 Tab 组件在结构上分为两个明确的部分:
Tab 控制区 (
- ):用户点击的“标签”列表。
Tab 内容区 (
它们之间最关键的“契约”是:我们必须有一种方式将“控制按钮”和“内容面板”关联起来。
我们将使用 data-* 属性作为这个“钩子”。
<!-- 1. Tab 控制区 -->
<!--
使用 ul 列表在语义上最适合导航/控制列表。
role="tablist" 是一个无障碍 (accessibility) 属性,
告诉屏幕阅读器这是一个 Tab 列表。
-->
<ul class="tab-controls" role="tablist">
<!--
默认激活第一个。
注意 data-tab-target 属性,它的值是一个 CSS 选择器 (#tab-panel-1),
它精确地指向了它应该控制的那个内容面板的 ID。
-->
<li class.="tab-control is-active" role="tab" data-tab-target="#tab-panel-1">
个人资料
</li>
<li class="tab-control" role="tab" data-tab-target="#tab-panel-2">
账户设置
</li>
<li class="tab-control" role="tab" data-tab-target="#tab-panel-3">
安全
</li>
</ul>
<!-- 2. Tab 内容区 -->
<div class="tab-content-wrapper">
<!--
内容面板1。
id 必须与 data-tab-target 的值对应。
默认激活第一个。
-->
<div class="tab-panel is-active" id="tab-panel-1" role="tabpanel">
<h3>个人资料面板</h3>
<p>这里是用户的基本信息...</p>
<div class="form-group">
<label>用户名:</label>
<input type="text" value="FrontendPro" disabled>
</div>
</div>
<!-- 内容面板2 -->
<div class="tab-panel" id="tab-panel-2" role="tabpanel">
<h3>账户设置面板</h3>
<p>这里是账户设置相关表单...</p>
<div class="form-group">
<label>电子邮箱:</label>
<input type="email" value="user@example.com">
</div>
<button>更新邮箱</button>
</div>
<!-- 内容面板3 -->
<div class="tab-panel" id="tab-panel-3" role="tabpanel">
<h3>安全面板</h3>
<p>修改您的密码或启用双因素认证。</p>
<div class="form-group">
<label>新密码:</label>
<input type="password">
</div>
</div>
</div>
第二部分:CSS 样式 (皮肤)
CSS 的核心职责是定义两个状态:
.is-active:激活的 Tab 控制按钮和激活的内容面板应该是什么样子。
默认状态:未激活的 Tab 和面板应该是什么样子(通常是 display: none)。
/* --- 基础容器样式 --- */
.tabs-container {
width: 600px;
max-width: 100%;
margin: 40px auto;
border: 1px solid #dfe4ea;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
background-color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* --- 1. Tab 控制区样式 --- */
.tab-controls {
display: flex;
padding-left: 0;
margin: 0;
border-bottom: 1px solid #dfe4ea;
background-color: #f8f9fa;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.tab-control {
list-style: none; /* 移除
- 的小黑点 /
padding: 14px 20px;
cursor: pointer;
font-weight: 500;
color: #57606f;
position: relative; / 用于激活状态的下划线 /
border-bottom: 3px solid transparent; / 预留底部边框空间 */
transition: all 0.2s ease-in-out;
}
.tab-control:hover {
background-color: #f1f2f6;
color: #2f3542;
}
/* --- 激活状态 (.is-active) --- /
/ 这是 CSS 的核心:为激活的 Tab 设置不同的样式 /
.tab-control.is-active {
color: #007bff;
font-weight: 600;
/ 激活的下划线 */
border-bottom-color: #007bff;
}
/* --- 2. Tab 内容区样式 --- */
.tab-content-wrapper {
padding: 25px;
line-height: 1.6;
}
/* 这是 JS 交互的核心:
默认情况下,所有面板都隐藏。
/
.tab-panel {
display: none;
animation: fadeIn 0.3s ease-in-out; / 添加一个简单的淡入动画 */
}
/* 这个类由 JavaScript 添加/移除。
它会覆盖 .tab-panel 的 "display: none",
将其显示出来。
*/
.tab-panel.is-active {
display: block;
}
/* 简单的淡入动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* --- 内部表单的简单样式 --- /
.form-group {
margin: 15px 0;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box; / 保证 padding 不会撑破宽度 */
}
第三部分:JS 交互逻辑 (大脑)
JavaScript 的工作就像一个“交通指挥”。它不需要关心 Tab 长什么样,它只关心:
用户点击了哪个 Tab?
我应该隐藏所有面板。
我应该显示与被点击的 Tab 对应的那个面板。
我应该移除所有 Tab 的激活样式。
我应该给被点击的 Tab 加上激活样式。
// 这是一个最佳实践:
// 总是等待 DOM 内容完全加载后才执行 JS
document.addEventListener('DOMContentLoaded', () => {
// 1. 抓取所有的 Tab 控制按钮
// 我们使用 querySelectorAll 来获取一个“节点列表”
const allTabControls = document.querySelectorAll('.tab-control');
// 2. 抓取所有的内容面板
const allTabPanels = document.querySelectorAll('.tab-panel');
// 3. 为每一个控制按钮绑定点击事件
// 我们使用 forEach 来遍历这个列表
allTabControls.forEach((clickedControl) => {
clickedControl.addEventListener('click', () => {
// --- 核心逻辑开始 ---
// 步骤 A:移除所有 Tab 和 Panel 的 "is-active" 类
// (1) 先取消所有 Tab 按钮的激活状态
allTabControls.forEach((control) => {
control.classList.remove('is-active');
});
// (2) 再隐藏所有内容面板
allTabPanels.forEach((panel) => {
panel.classList.remove('is-active');
});
// 步骤 B:给被点击的 Tab 和其对应的 Panel 加上 "is-active" 类
// (1) 激活被点击的 Tab 按钮
clickedControl.classList.add('is-active');
// (2) 找出对应的面板并显示它
// 我们从被点击的按钮上读取 data-tab-target 属性 (e.g., "#tab-panel-1")
const targetPanelId = clickedControl.dataset.tabTarget;
const targetPanel = document.querySelector(targetPanelId);
// 健壮性检查:如果找到了目标面板,就显示它
if (targetPanel) {
targetPanel.classList.add('is-active');
}
// --- 核心逻辑结束 ---
});
});
});
总结
恭喜!您刚刚构建了一个完整、专业且健壮的 Tab 选项卡组件。
我们学习到了:
HTML: 如何使用 data-tab-target 和 id 来建立“控制”与“内容”的语义连接。
CSS: 如何使用 .is-active 修饰符类来管理“显示/隐藏”和“激活/非激活”的样式。
JS: 如何充当“调度者”,通过循环和 classList 来切换 CSS 类,而不是直接操作样式(style.display = 'none')。
这个模式(HTML 钩子 -> CSS 状态 -> JS 切换)是现代前端开发的基石。

浙公网安备 33010602011771号