表单 — CodeRoadMapHTML 表单
这一课你将能做到
- 写出带 label、fieldset、legend 的完整语义化表单,知道 label 的两种关联方式
- 使用 text/email/password/number/date/file 等 10+ 种 input 类型,并配置 placeholder、required、pattern、min/max 等验证属性
- 用 select/optgroup/option、textarea、button 构建复杂表单控件,理解 GET/POST 提交差异和文件上传的 enctype
为什么要学
表单是用户与网站交互的唯一入口:登录注册、搜索、下单、评论、文件上传。一个表单做不好,用户流失、数据错误、安全漏洞全来了。HTML5 提供了丰富的 input 类型和内置验证,但很多初学者还在用 <input type="text"> 收邮箱、用 onclick 做提交按钮——既浪费浏览器能力,又增加 JS 负担。
人话:表单是网站的「收银台」。收银台设计得好,用户顺畅结账;设计得烂,用户到门口掉头就走。
先建立图像
表单的三大核心关系:
form(容器)
├── action:提交到哪
├── method:GET / POST
├── enctype:application/x-www-form-urlencoded(默认)
│ / multipart/form-data(文件上传)
│ / text/plain(纯文本,几乎不用)
│
├── label(标签)──for──→ input(控件)
│ │ ├── type:text/email/password/...
│ │ ├── name:提交时的 key
│ │ ├── value:默认值
│ │ ├── required/pattern/min/max:验证
│ │ └── placeholder:提示
│ └── 或 label 包裹 input
│
├── fieldset(分组)── legend(组标题)
│
└── button(提交)
├── type="submit":提交表单
├── type="button":纯按钮,配合 JS
└── type="reset":重置
跟着做
步骤 1:最小可用表单
新建 forms.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单演示</title>
</head>
<body>
<form action="/submit" method="POST">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<button type="submit">注册</button>
</form>
</body>
</html>
核心规则:
label for="id" 与 input id="id" 关联,点击 label 聚焦 input。
name 是提交时的 key,没有 name 的控件不会被提交。
required 触发浏览器内置必填验证。
步骤 2:label 的两种写法
<!-- 方式一:for/id 关联(推荐,结构清晰) -->
<label for="pwd">密码:</label>
<input type="password" id="pwd" name="password">
<!-- 方式二:label 包裹(隐式关联,无需 id) -->
<label>
确认密码:
<input type="password" name="confirm">
</label>
两种方式等价,for/id 更灵活(label 可放在任意位置),包裹式更简洁。
步骤 3:常用 input 类型
<form action="/submit" method="POST">
<!-- 文本 -->
<label for="name">姓名:</label>
<input type="text" id="name" name="name" placeholder="请输入姓名" required>
<!-- 邮箱:自动格式验证 -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" placeholder="name@example.com" required>
<!-- 密码:输入掩码 -->
<label for="pwd">密码:</label>
<input type="password" id="pwd" name="pwd" minlength="8" required>
<!-- 数字:范围限制 -->
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="18" max="120">
<!-- 日期:日期选择器 -->
<label for="birthday">生日:</label>
<input type="date" id="birthday" name="birthday">
<!-- 电话:移动端弹出数字键盘 -->
<label for="phone">手机:</label>
<input type="tel" id="phone" name="phone" pattern="[0-9]{11}">
<!-- URL:自动验证格式 -->
<label for="website">个人网站:</label>
<input type="url" id="website" name="website">
<!-- 搜索:有清空按钮 -->
<label for="search">搜索:</label>
<input type="search" id="search" name="q">
<!-- 颜色:取色器 -->
<label for="color">喜欢的颜色:</label>
<input type="color" id="color" name="color">
<!-- 范围:滑块 -->
<label for="volume">音量:</label>
<input type="range" id="volume" name="volume" min="0" max="100" value="50">
<!-- 文件:需配合 enctype -->
<label for="avatar">头像:</label>
<input type="file" id="avatar" name="avatar" accept="image/*">
<button type="submit">提交</button>
</form>
步骤 4:textarea、select、radio、checkbox
<!-- 多行文本 -->
<label for="bio">个人简介:</label>
<textarea id="bio" name="bio" rows="4" cols="50" maxlength="500" placeholder="介绍一下自己"></textarea>
<!-- 下拉选择 -->
<label for="city">城市:</label>
<select id="city" name="city" required>
<option value="">请选择</option>
<optgroup label="直辖市">
<option value="bj">北京</option>
<option value="sh">上海</option>
</optgroup>
<optgroup label="省份">
<option value="zj">浙江</option>
<option value="js">江苏</option>
</optgroup>
</select>
<!-- 单选:name 相同互斥 -->
<fieldset>
<legend>性别</legend>
<label><input type="radio" name="gender" value="male" required> 男</label>
<label><input type="radio" name="gender" value="female"> 女</label>
<label><input type="radio" name="gender" value="other"> 其他</label>
</fieldset>
<!-- 复选:name 相同成组 -->
<fieldset>
<legend>兴趣爱好</legend>
<label><input type="checkbox" name="hobby" value="coding"> 编程</label>
<label><input type="checkbox" name="hobby" value="reading"> 阅读</label>
<label><input type="checkbox" name="hobby" value="sports"> 运动</label>
</fieldset>
步骤 5:HTML5 验证
<form action="/submit" method="POST" novalidate>
<!-- novalidate 禁用浏览器默认验证,交给 JS 自定义 -->
<label for="username">用户名(4-16位字母数字):</label>
<input type="text" id="username" name="username"
pattern="[a-zA-Z0-9]{4,16}"
title="4-16位字母或数字"
required>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<label for="pwd">密码(至少8位):</label>
<input type="password" id="pwd" name="pwd" minlength="8" required>
<label for="confirm">确认密码:</label>
<input type="password" id="confirm" name="confirm" required>
<button type="submit">注册</button>
</form>
<script>
// 自定义验证:两次密码一致
document.querySelector('form').addEventListener('submit', function(e) {
const pwd = document.getElementById('pwd');
const confirm = document.getElementById('confirm');
if (pwd.value !== confirm.value) {
e.preventDefault();
confirm.setCustomValidity('两次密码不一致');
confirm.reportValidity();
} else {
confirm.setCustomValidity('');
}
});
</script>
required:必填
pattern:正则表达式(不写 ^ 和 $,浏览器自动加)
minlength / maxlength:文本长度
min / max:数字、日期范围
title:验证失败时的提示文本
novalidate:form 上禁用浏览器验证,交给 JS
步骤 6:文件上传
<form action="/upload" method="POST" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" id="file" name="file" accept=".jpg,.png,.pdf">
<!-- 多文件 -->
<label for="files">多文件:</label>
<input type="file" id="files" name="files" multiple>
<button type="submit">上传</button>
</form>
enctype="multipart/form-data":文件上传必须,否则后端收不到文件内容。
accept:限制可选文件类型,image/*、.jpg,.png、application/pdf。
multiple:允许多选。
讲清楚要点
- name 是提交的门票:没有 name 的 input、select、textarea 不会出现在提交数据中。radio 和 checkbox 靠相同 name 分组,value 是提交的值。
- label 不只是文字:for/id 关联后,点击 label 聚焦/勾选控件,扩大点击区域,屏幕阅读器朗读 label 作为控件名称。
- GET vs POST:GET 数据在 URL(
?key=value),有长度限制、可见、可缓存,用于搜索;POST 数据在请求体,无长度限制、不可见,用于提交敏感数据或文件。
- 浏览器验证是客户端验证:可被绕过,后端必须二次验证。HTML5 验证的价值是即时反馈,减少无效请求。
- enctype 决定编码方式:默认
application/x-www-form-urlencoded(键值对编码);文件上传必须 multipart/form-data;text/plain 几乎不用(不换行、不编码,调试用)。
你可能会懵的地方
-
placeholder 能替代 label 吗?
不能。placeholder 是提示,输入后消失;label 是名称,始终可见。只用 placeholder 的表单,用户输到一半忘了要填什么,且屏幕阅读器可能读不到。正确做法:label + placeholder 配合。
-
button 和 input type="submit" 什么区别?
button 更灵活,内部可以放 HTML(图标、多行文字);input 是单标签,只能纯文本。现代开发统一用 <button type="submit">。
-
radio 没选时提交什么?
什么都不提交。如果需要默认值,给某个 radio 加 checked 属性。
-
select 的 option 没有 value 提交什么?
提交 option 的文本内容。建议始终写 value,与显示文本解耦(如 value="bj" 显示「北京」)。
-
表单里按回车自动提交?
当表单只有一个 text input 时,回车默认触发 submit。阻止:input 加 onkeydown="if(event.key==='Enter')event.preventDefault()",或改用 button type="button" + JS 提交。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 所有输入都用 type="text" | 无格式验证,移动端键盘不优化 | 按语义用 email/number/tel/date 等 |
| 无 label,只有 placeholder | 可访问性差,输入后忘记字段含义 | label + placeholder 配合 |
| 文件上传忘改 enctype | 后端收不到文件,只收到文件名 | enctype="multipart/form-data" |
| 密码框 autocomplete="off" | 浏览器仍可能自动填充,且用户体验差 | 敏感操作前加二次验证,不依赖 autocomplete |
| 用 GET 提交密码 | 密码出现在 URL 和浏览器历史 | 敏感数据一律 POST |
| 前端验证通过就入库 | 可被 curl/Postman 绕过 | 后端必须独立验证 |
卡点急救
- 表单提交后页面刷新,数据丢失:默认行为。用 JS
event.preventDefault() + fetch/Ajax 提交,或后端返回重定向。
- pattern 验证不通过,提示看不懂:
title 属性写人话提示,如 title="请输入 11 位手机号"。
- 文件上传后后端收不到:检查 enctype、input 的 name 是否为空、文件大小是否超后端限制(Spring Boot 默认 1MB)。
- select 想默认选中某一项:
<option selected>,不是 checked。
- checkbox 只提交选中的值:未选中的 checkbox 不提交。后端需要「未选中=0」时,前端隐藏 input 同步,或后端按 name 判空处理。
面试常问
-
GET 和 POST 的区别?
GET:参数在 URL 查询字符串,有长度限制(约 2KB-8KB 浏览器/服务器限制),参数可见,可缓存,幂等(多次执行效果相同),用于获取资源。POST:参数在请求体,无长度限制,不可见,不可缓存,非幂等,用于创建/更新资源。GET 不应改变服务器状态。
-
表单验证有哪些方式?
① HTML5 内置:required、pattern、min/max、type=email 等;② JS 自定义:Constraint Validation API(setCustomValidity);③ 后端验证:必须,前端验证只是用户体验优化,可被绕过。
-
label 的作用?
① 可访问性:屏幕阅读器朗读 label 作为控件名称;② 用户体验:点击 label 聚焦/勾选关联控件,扩大点击区域;③ 语义化:明确控件用途。
-
文件上传的原理?
表单设置 enctype="multipart/form-data",浏览器将文件二进制数据按 boundary 分割封装在请求体中。后端用 multipart/form-data 解析器(如 Spring 的 MultipartFile)接收。文件大小受前端 accept、后端配置(spring.servlet.multipart.max-file-size)、Web 服务器(Nginx client_max_body_size)三重限制。
自测三问
- 写出一个包含姓名(必填)、邮箱(必填+格式验证)、年龄(18-120)、个人简介(textarea)的完整表单。
- 为什么文件上传的 form 必须设置 enctype?不设置会怎样?
- radio 和 checkbox 的 name 属性分别起什么作用?
小结一句话
表单是用户数据的入口:label 给控件命名,type 给输入定格式,验证属性给数据把关,enctype 给文件开路——细节不到位,数据和安全都出问题。
延伸