02-表单元素详解
📋 学习目标
掌握HTML表单的基本结构
熟悉各类表单输入控件
理解表单验证机制
学习表单最佳实践
📝 表单基础
基本结构
<form action="/submit" method="POST" enctype="multipart/form-data">
<!-- 表单控件 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<button type="submit">提交</button>
</form>
form属性详解
<!-- action:提交地址 -->
<form action="/api/submit">
<!-- method:提交方法 -->
<form method="GET"> <!-- 默认,数据在URL中 -->
<form method="POST"> <!-- 数据在请求体中 -->
<!-- enctype:编码类型 -->
<form enctype="application/x-www-form-urlencoded"> <!-- 默认 -->
<form enctype="multipart/form-data"> <!-- 文件上传必需 -->
<form enctype="text/plain"> <!-- 纯文本 -->
<!-- target:提交目标窗口 -->
<form target="_blank"> <!-- 新窗口 -->
<form target="_self"> <!-- 当前窗口,默认 -->
<!-- autocomplete:自动完成 -->
<form autocomplete="on"> <!-- 启用,默认 -->
<form autocomplete="off"> <!-- 禁用 -->
<!-- novalidate:禁用HTML5验证 -->
<form novalidate>
🔤 文本输入
基本文本框
<!-- 单行文本 -->
<input type="text"
name="username"
id="username"
placeholder="请输入用户名"
value="默认值"
maxlength="20"
minlength="3"
pattern="[A-Za-z0-9]+"
required
autofocus
autocomplete="username">
<!-- 多行文本 -->
<textarea name="comment"
id="comment"
rows="5"
cols="40"
placeholder="请输入评论"
maxlength="500"
required></textarea>
<!-- 不可调整大小的textarea -->
<textarea style="resize: none;"></textarea>
专用输入类型
<!-- 密码 -->
<input type="password"
name="password"
autocomplete="current-password"
minlength="8"
required>
<!-- 邮箱(自动验证格式) -->
<input type="email"
name="email"
placeholder="example@domain.com"
autocomplete="email"
required>
<!-- 电话 -->
<input type="tel"
name="phone"
pattern="[0-9]{3}-[0-9]{4}-[0-9]{4}"
placeholder="138-0000-0000">
<!-- URL -->
<input type="url"
name="website"
placeholder="https://example.com"
pattern="https://.*">
<!-- 搜索框 -->
<input type="search"
name="q"
placeholder="搜索...">
🔢 数字和日期
数字输入
<!-- 数字 -->
<input type="number"
name="age"
min="0"
max="120"
step="1"
value="18">
<!-- 范围滑块 -->
<input type="range"
name="volume"
min="0"
max="100"
step="5"
value="50">
<output for="volume">50</output>
<!-- 实时显示range值 -->
<input type="range" id="slider" min="0" max="100" value="50"
oninput="document.getElementById('output').value = this.value">
<output id="output">50</output>
日期和时间
<!-- 日期 -->
<input type="date"
name="birthday"
min="1900-01-01"
max="2025-12-31">
<!-- 时间 -->
<input type="time"
name="appointment"
step="900"> <!-- 15分钟间隔 -->
<!-- 日期时间(本地) -->
<input type="datetime-local"
name="meeting"
step="1"> <!-- 包含秒 -->
<!-- 月份 -->
<input type="month" name="start-month">
<!-- 周 -->
<input type="week" name="week">
<!-- 设置默认值 -->
<input type="date" value="2025-10-21">
<input type="time" value="14:30">
<input type="datetime-local" value="2025-10-21T14:30">
☑️ 选择控件
复选框
<!-- 单个复选框 -->
<input type="checkbox"
name="agree"
id="agree"
value="yes"
required>
<label for="agree">我同意服务条款</label>
<!-- 多个复选框 -->
<fieldset>
<legend>兴趣爱好:</legend>
<input type="checkbox" name="hobbies" id="reading" value="reading">
<label for="reading">阅读</label>
<input type="checkbox" name="hobbies" id="sports" value="sports">
<label for="sports">运动</label>
<input type="checkbox" name="hobbies" id="music" value="music" checked>
<label for="music">音乐</label>
</fieldset>
<!-- 不确定状态(通过JavaScript) -->
<input type="checkbox" id="indeterminate">
<script>
document.getElementById('indeterminate').indeterminate = true;
</script>
单选按钮
<fieldset>
<legend>性别:</legend>
<input type="radio" name="gender" id="male" value="male" checked>
<label for="male">男</label>
<input type="radio" name="gender" id="female" value="female">
<label for="female">女</label>
<input type="radio" name="gender" id="other" value="other">
<label for="other">其他</label>
</fieldset>
要点:
同一组单选按钮的name属性必须相同
使用label提升用户体验(点击文字也能选中)
checked属性设置默认选中项
下拉选择
<!-- 基本下拉框 -->
<select name="city" id="city" required>
<option value="">请选择城市</option>
<option value="beijing">北京</option>
<option value="shanghai" selected>上海</option>
<option value="guangzhou">广州</option>
<option value="shenzhen">深圳</option>
</select>
<!-- 分组选项 -->
<select name="country">
<optgroup label="亚洲">
<option value="cn">中国</option>
<option value="jp">日本</option>
<option value="kr">韩国</option>
</optgroup>
<optgroup label="欧洲">
<option value="uk">英国</option>
<option value="fr">法国</option>
<option value="de">德国</option>
</optgroup>
</select>
<!-- 多选下拉框 -->
<select name="skills" multiple size="5">
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="js">JavaScript</option>
<option value="react">React</option>
<option value="vue">Vue</option>
</select>
<!-- 禁用选项 -->
<select name="role">
<option value="user">普通用户</option>
<option value="admin" disabled>管理员(不可选)</option>
</select>
📁 文件上传
文件输入
<!-- 单文件上传 -->
<input type="file"
name="avatar"
id="avatar"
accept="image/*">
<!-- 多文件上传 -->
<input type="file"
name="photos"
multiple
accept="image/png, image/jpeg, image/jpg">
<!-- 指定文件类型 -->
<input type="file" accept=".pdf,.doc,.docx">
<input type="file" accept="image/*"> <!-- 所有图片 -->
<input type="file" accept="video/*"> <!-- 所有视频 -->
<input type="file" accept="audio/*"> <!-- 所有音频 -->
<!-- 拍照上传(移动端) -->
<input type="file" accept="image/*" capture="camera">
<input type="file" accept="video/*" capture="camcorder">
文件上传最佳实践
<form enctype="multipart/form-data" method="POST">
<label for="file-upload">选择文件:</label>
<input type="file"
id="file-upload"
name="file"
accept="image/png,image/jpeg"
required>
<!-- 文件大小限制通过JavaScript验证 -->
<p class="help-text">支持PNG、JPEG格式,大小不超过5MB</p>
<button type="submit">上传</button>
</form>
<script>
document.getElementById('file-upload').addEventListener('change', function(e) {
const file = e.target.files[0];
const maxSize = 5 * 1024 * 1024; // 5MB
if (file && file.size > maxSize) {
alert('文件大小不能超过5MB');
this.value = '';
}
});
</script>
🎨 其他输入类型
颜色选择器
<input type="color"
name="theme-color"
value="#ff6b6b">
隐藏字段
<input type="hidden" name="user_id" value="12345">
<input type="hidden" name="csrf_token" value="abc123xyz">
🔘 按钮
按钮类型
<!-- 提交按钮 -->
<button type="submit">提交</button>
<input type="submit" value="提交">
<!-- 重置按钮 -->
<button type="reset">重置</button>
<input type="reset" value="重置">
<!-- 普通按钮 -->
<button type="button" onclick="doSomething()">点击</button>
<input type="button" value="点击" onclick="doSomething()">
<!-- 图片按钮 -->
<input type="image" src="submit-button.png" alt="提交">
推荐使用<button>标签:
可以包含HTML内容(图标、文字等)
更好的样式控制
更语义化
<!-- 带图标的按钮 -->
<button type="submit">
<svg>...</svg>
提交表单
</button>
📋 表单分组
fieldset和legend
<form>
<fieldset>
<legend>基本信息</legend>
<label for="name">姓名:</label>
<input type="text" id="name" name="name">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email">
</fieldset>
<fieldset disabled>
<legend>高级设置(已禁用)</legend>
<input type="checkbox" name="advanced" value="1">
<label>启用高级功能</label>
</fieldset>
</form>
标签关联
<!-- 方式1:for属性关联 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username">
<!-- 方式2:包裹input -->
<label>
用户名:
<input type="text" name="username">
</label>
<!-- 多个label关联同一input -->
<label for="email-input">邮箱地址:</label>
<input type="email" id="email-input" name="email" aria-labelledby="email-label">
<label id="email-label">用于接收通知</label>
✅ HTML5表单验证
内置验证属性
<!-- required:必填 -->
<input type="text" name="username" required>
<!-- pattern:正则验证 -->
<input type="text"
pattern="[A-Za-z0-9]{6,12}"
title="6-12位字母或数字">
<!-- minlength/maxlength:长度限制 -->
<input type="text" minlength="3" maxlength="20">
<!-- min/max:数值范围 -->
<input type="number" min="1" max="100">
<input type="date" min="2025-01-01" max="2025-12-31">
<!-- step:步进值 -->
<input type="number" step="0.01"> <!-- 允许小数 -->
<input type="range" step="5"> <!-- 5的倍数 -->
验证状态CSS伪类
/* 必填字段 */
input:required {
border-color: #ff6b6b;
}
/* 可选字段 */
input:optional {
border-color: #999;
}
/* 验证通过 */
input:valid {
border-color: #51cf66;
}
/* 验证失败 */
input:invalid {
border-color: #ff6b6b;
}
/* 范围内 */
input:in-range {
border-color: #51cf66;
}
/* 超出范围 */
input:out-of-range {
border-color: #ff6b6b;
}
自定义验证消息
<input type="email"
id="email"
required
oninvalid="this.setCustomValidity('请输入有效的邮箱地址')"
oninput="this.setCustomValidity('')">
<script>
const emailInput = document.getElementById('email');
emailInput.addEventListener('invalid', function(e) {
if (this.validity.valueMissing) {
this.setCustomValidity('邮箱不能为空');
} else if (this.validity.typeMismatch) {
this.setCustomValidity('请输入正确的邮箱格式');
}
});
emailInput.addEventListener('input', function() {
this.setCustomValidity('');
});
</script>
JavaScript验证API
const form = document.getElementById('myForm');
const input = document.getElementById('myInput');
// 检查单个字段
if (input.checkValidity()) {
console.log('验证通过');
} else {
console.log('验证失败:', input.validationMessage);
}
// 检查整个表单
if (form.checkValidity()) {
console.log('表单验证通过');
} else {
console.log('表单验证失败');
}
// 手动触发验证
form.reportValidity(); // 显示验证错误信息
// validity对象属性
console.log(input.validity.valid); // 是否有效
console.log(input.validity.valueMissing); // 是否缺少必填值
console.log(input.validity.typeMismatch); // 类型不匹配
console.log(input.validity.patternMismatch); // 正则不匹配
console.log(input.validity.tooLong); // 超过maxlength
console.log(input.validity.tooShort); // 小于minlength
console.log(input.validity.rangeUnderflow); // 小于min
console.log(input.validity.rangeOverflow); // 大于max
console.log(input.validity.stepMismatch); // 不符合step
🎯 完整表单示例
用户注册表单
<form id="registerForm" action="/register" method="POST" novalidate>
<h2>用户注册</h2>
<!-- 用户名 -->
<div class="form-group">
<label for="username">用户名 *</label>
<input type="text"
id="username"
name="username"
pattern="[A-Za-z0-9_]{4,16}"
title="4-16位字母、数字或下划线"
required
autocomplete="username">
<span class="error-message"></span>
</div>
<!-- 邮箱 -->
<div class="form-group">
<label for="email">邮箱 *</label>
<input type="email"
id="email"
name="email"
required
autocomplete="email">
<span class="error-message"></span>
</div>
<!-- 密码 -->
<div class="form-group">
<label for="password">密码 *</label>
<input type="password"
id="password"
name="password"
minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="至少8位,包含大小写字母和数字"
required
autocomplete="new-password">
<span class="error-message"></span>
</div>
<!-- 确认密码 -->
<div class="form-group">
<label for="confirm-password">确认密码 *</label>
<input type="password"
id="confirm-password"
name="confirm_password"
required
autocomplete="new-password">
<span class="error-message"></span>
</div>
<!-- 生日 -->
<div class="form-group">
<label for="birthday">生日</label>
<input type="date"
id="birthday"
name="birthday"
min="1900-01-01"
max="2025-12-31">
</div>
<!-- 性别 -->
<fieldset>
<legend>性别</legend>
<label><input type="radio" name="gender" value="male"> 男</label>
<label><input type="radio" name="gender" value="female"> 女</label>
<label><input type="radio" name="gender" value="other"> 其他</label>
</fieldset>
<!-- 兴趣 -->
<fieldset>
<legend>兴趣爱好</legend>
<label><input type="checkbox" name="hobbies" value="reading"> 阅读</label>
<label><input type="checkbox" name="hobbies" value="sports"> 运动</label>
<label><input type="checkbox" name="hobbies" value="music"> 音乐</label>
<label><input type="checkbox" name="hobbies" value="travel"> 旅游</label>
</fieldset>
<!-- 同意条款 -->
<div class="form-group">
<label>
<input type="checkbox" name="agree" required>
我已阅读并同意<a href="/terms">服务条款</a>
</label>
</div>
<!-- 按钮 -->
<div class="form-actions">
<button type="submit">注册</button>
<button type="reset">重置</button>
</div>
</form>
<script>
const form = document.getElementById('registerForm');
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirm-password');
// 密码确认验证
confirmPassword.addEventListener('input', function() {
if (this.value !== password.value) {
this.setCustomValidity('两次密码输入不一致');
} else {
this.setCustomValidity('');
}
});
// 表单提交
form.addEventListener('submit', function(e) {
e.preventDefault();
if (this.checkValidity()) {
const formData = new FormData(this);
console.log('提交数据:', Object.fromEntries(formData));
// 实际项目中这里发送AJAX请求
} else {
this.reportValidity();
}
});
</script>
📚 实践练习
练习1:登录表单
创建一个登录表单,包含:
用户名/邮箱输入
密码输入
记住我复选框
提交和忘记密码链接
实现客户端验证
练习2:问卷调查表单
创建一个调查问卷,包含:
单选题(满意度评分)
多选题(产品功能)
文本题(建议反馈)
提交后显示结果
练习3:文件上传表单
创建文件上传表单,包含:
图片预览功能
文件大小和类型验证
上传进度显示
多文件上传支持
💡 最佳实践
1. 可访问性
为所有表单控件添加label
使用fieldset和legend分组
提供清晰的错误提示
支持键盘导航
2. 用户体验
使用合适的输入类型(自动调起键盘)
提供placeholder和提示信息
实时验证反馈
合理使用autocomplete
3. 安全性
服务端必须验证所有数据
使用CSRF令牌
密码字段使用type=“password”
敏感操作需要二次确认
4. 性能优化
避免过度的实时验证
使用防抖处理输入事件
适当使用HTML5验证而非JavaScript
表单数据序列化优化