第 03 节 · 表单与输入控件
第 03 节 · 表单与输入控件
📌 版本信息:基于 HTML Living Standard(WHATWG 维护,2026-09-06 核对) 📚 来源:MDN Web 表单 | MDN
<input>类型 | MDN<button>文档
从地图应用的查询面板说起
地图不是一张孤立的图片,用户总要围着它操作:输入半径筛选周边 POI、下拉选择底图来源、用日期控件统计某段时间的数据、上传一份 Shapefile、登录管理后台。这些输入装置放在页面上,背后几乎都是同一套东西——表单。
表单比文字和列表复杂的地方在于:它不只是“显示输入框”,还定义了数据怎么组织、怎么校验、怎么交出去。本节就从地图应用里最常见的查询面板入手,把表单的整套数据流弄清楚。
这一节讲什么
本节讲表单的三要素(form、input、button)、常用输入控件、label 与 name 的职责,以及浏览器自带的表单校验。学完这一节可以做到:
- 写出一个结构完整的登录、注册或查询表单
- 分清
id、name、type各自负责什么,不会把数据键名写丢 - 正确分组单选按钮,处理复选框、下拉框、日期、文件和数字输入
- 用 HTML 属性完成必填、长度、范围和格式校验
- 说清前端校验为什么不能替代后端校验
表单的数据流
表单是页面收集用户输入、再把它交给服务器的装置。一套最简表单长这样:
<form action="/api/search" method="post">
<label for="radius">搜索半径(公里)</label>
<input type="number" id="radius" name="radius" min="1" max="100" />
<button type="submit">查询</button>
</form>
提交发生时,浏览器做三件事:收集表单里带 name 的输入值、按 method 指定的方式组织数据、把请求发到 action 指定的地址。数据并没有在页面里存放,而是被序列化成 name=value 的键值对交出去,例如 radius=5&layer=poi。
两个最基础属性的分工:
action:数据交给谁。一个 Web 项目里,这里通常是后端接口地址。method:怎么交。get会把键值对拼到网址后面,适合查询和可以分享的链接;post把数据放在请求体里,适合密码、文件和大段内容。涉及密码时一定用post。
id、name、label:三个容易被搞混的伙伴
🔁 把
label、id、name放在一起看,就是一个“怎么称呼、页面怎么找、数据怎么交”的三角关系。
<label for="search">定位区域</label>
<input type="text" id="search" name="keyword" required />
| 属性 | 服务对象 | 漏掉的后果 |
|---|---|---|
id |
页面内部:label 指向它、JS 查找它 |
点文字不能聚焦输入框,无障碍信息也会断掉 |
name |
提交数据:作为请求里的键名 | 用户填了,后端却收不到 |
for |
把 label 和 id 绑定成一对 |
点击“定位区域”四个字没有反应 |
这里是最容易记反的一组概念。id 是给页面内部用的“身份牌”,name 是给数据传输用的“字段名”,两者经常长得很像,但不能互相替代。写表单时先问一句:这个值之后要交出去吗?要交出去就必须有 name。
label 的 for 和输入框的 id 必须成对。绑定的直接收益有两个:鼠标点标签文字能聚焦输入框,屏幕阅读器能把标签和输入框读成一句完整的话。
input 全家族
<input> 是一族控件:一个标签通过 type 变身成不同形态,同时影响手机弹出的键盘类型和浏览器的默认校验。
<!-- 文本与账号类 -->
<input type="text" /> <!-- 单行文本:搜索词、用户名 -->
<input type="password" /> <!-- 密码:显示为圆点 -->
<input type="email" /> <!-- 邮箱:自带格式校验 -->
<input type="search" /> <!-- 搜索框:部分浏览器带清空按钮 -->
<input type="tel" /> <!-- 电话号码 -->
<input type="url" /> <!-- 网址,需 http(s) 开头 -->
<!-- 数字与范围:查询面板的主角 -->
<input type="number" min="0" max="100" step="1" />
<input type="range" min="0" max="100" value="50" />
<!-- 日期时间 -->
<input type="date" />
<input type="datetime-local" />
<!-- 选择与状态 -->
<input type="checkbox" name="layer" value="roads" />
<input type="radio" name="basemap" value="gaode" />
<input type="hidden" name="token" value="abc123" />
<!-- 文件与其他 -->
<input type="file" accept=".zip,.shp" />
<input type="color" />
几个要点:
number的min、max、step不只是界面限制,提交时浏览器也会按规则校验。- 复选框适合“可多选”的图层开关;单选按钮靠相同
name组成互斥的一组。name不同,它们就各算各的,这点很容易在写“底图三选一”时翻车。 hidden输入框会把值悄悄交给后端,但“隐藏”不等于“保密”,任何页面源码里的内容用户都能看到,不能放密钥。file的accept只是文件选择器里的推荐类型,不是安全过滤,后端仍要校验文件内容。
textarea、select 和 button
<!-- 多行文本:初始内容写在标签之间,不是 value 属性 -->
<textarea rows="4" placeholder="补充说明…"></textarea>
<!-- 下拉框:option 的 value 是提交值,标签中间是显示文字 -->
<select name="province">
<option value="">-- 请选择省份 --</option>
<option value="hb" selected>湖北</option>
<option value="gd">广东</option>
</select>
<!-- 按钮三种类型 -->
<button type="submit">提交</button>
<button type="reset">清空</button>
<button type="button">普通按钮</button>
select 和 option 拆开来看:option 的 value 是真正提交给后端的值,标签里显示的是用户看到的文字,两者可以不同;selected 表示默认选中,省略时浏览器默认选第一项。
按钮的 type 决定行为:submit 提交表单,reset 把表单恢复成初始值,button 是纯按钮,只等待 JS 接管。
HTML 自带的表单校验
不用写一行 JavaScript,浏览器就能在提交前拦截明显不合规则的数据:
<form>
<label for="name">用户名</label>
<input type="text" id="name" name="name" required minlength="2" maxlength="20" />
<label for="email">邮箱</label>
<input type="email" id="email" name="email" required />
<label for="lon">经度</label>
<input type="number" id="lon" name="lon" min="-180" max="180" step="0.000001" />
<label for="code">验证码</label>
<input type="text" id="code" name="code" pattern="[0-9]{6}" title="请输入 6 位数字" />
<label for="addr">地址</label>
<input type="text" id="addr" name="addr" placeholder="例如:武汉市洪山区" />
<button type="submit">查询</button>
</form>
这些属性各管一段:
required:必填;空着提交会被拦截。minlength/maxlength:文本长度范围。min/max:数字范围,经纬度输入很适合用它。pattern:按正则表达式校验,[0-9]{6}表示 6 位数字;title里的文字会在校验失败的气泡中显示。placeholder:未输入时的灰色提示,输入后消失,它不是控件名称,也不参与提交。
还有一个必须清醒的认知:前端校验只是体验优化。浏览器校验只约束普通用户的操作路径,任何绕过页面直接发 HTTP 请求的人都可以跳过它。数据是否合法、是否安全,最终由后端校验决定。
⚠️ 前端校验像商场门口的提示牌,后端校验才是仓库的入库检查,两者缺一不可。
易错点
- 忘记
name:这是表单新手最高频的错误。页面能正常显示、校验也能通过,但提交时根本没有这个字段,后端收到的是“空请求”。写完表单,对照请求参数检查一遍name。 placeholder当label用:提示文字在输入后消失,屏幕阅读器未必读它,空表单的可访问性也会变差。两者职责不同:label是控件的正式名称,placeholder是辅助提示。- 按钮的默认
type是submit:想在表单里放一个“定位当前位置”按钮,忘写type="button",点击后整张表单被提交、页面刷新。地图按钮几乎全用type="button"。 - 单选分组分错:同一组单选按钮必须共用
name;写两个不同的名字,它们就各自独立,用户能同时选中多个。 textarea初始内容写进value:textarea不是自闭合标签,初始文字写在标签之间,value属性对它不生效。- 把前端校验当安全防线:正则和
required只挡误操作,不挡恶意请求。后端校验才是数据完整性的最后一道闸门。
动手练习:03 登录注册表单
配套文件:02-web-basics/examples/03-登录注册表单.html。这一页模拟了一个地图平台常见的三个入口:登录、注册、底图偏好设置。偏好设置里的单选分组,就是以后地图应用底图切换面板的原型。
练习没有后端,提交会返回 404,这是正常现象。重点不是服务器响应,而是观察请求是怎么被组织起来的:打开 DevTools 的 Network 面板,点击提交,选中那条请求,在 Payload 或 Form Data 里能看到所有 name=value 键值对。
文件里的 TODO 和参考答案:
<!-- TODO A:登录表单加“记住我”复选框 -->
<div class="row">
<label for="remember">记住我</label>
<input type="checkbox" id="remember" name="remember" />
</div>
<!-- TODO B:注册邮箱改成带校验的 email 类型 -->
<input type="email" id="reg-email" name="email" required />
<!-- TODO C:补“确认密码”输入框。
注意:两次密码是否一致,HTML 本身无法比较,
这个校验要交给 JavaScript 或后端。 -->
<label for="reg-pass2">确认密码</label>
<input type="password" id="reg-pass2" name="confirm" minlength="6" />
为什么“确认密码”不交给 HTML?因为浏览器内置校验处理的是单个字段自身的规则,比如必填、长度、格式;比较两个字段的值属于字段间关系,超出 HTML 的能力范围。类似的还有“密码不能和用户名相同”“结束日期必须晚于开始日期”,这类逻辑在 JS 阶段会有专门的练习。
底图偏好区里有两组单选:name="basemap" 的一组管底图,name="theme" 的一组管主题,两组互不干扰。可以动手把 name 改成相同的值,观察两组突然变成一个组,再改回来,这个交互现象比背规则记得牢。
通关标准:
- 能说出
id和name的区别,并在代码里指出各自出现的理由 - 登录和注册表单提交时,Network 面板里能看到完整的键值对
-
required、minlength、email、pattern的校验气泡分别触发过 - 底图三选一和主题二选一互不干扰,并能解释分组靠什么实现
自测题
id和name分别干什么?忘掉哪一个会导致数据提交不上去?- 一组单选按钮靠什么形成“一组”?
- 表单里的按钮默认
type是什么?忘记设置会产生什么后果? placeholder和label有什么区别?为什么不能用前者替代后者?- 为什么说前端校验只是体验优化?
参考答案
id是页面内的唯一身份,供label和 JS 使用;name是提交数据的键名。忘记name,这个字段根本不会出现在请求里。- 相同的
name属性值。同一组单选用同一个名字,浏览器才把它们当作互斥的一组。 - 默认是
submit。忘记设置会把普通按钮当成提交按钮,点击后触发表单提交,页面跳转或刷新。 placeholder是输入前的灰色提示,输入后就消失,不参与提交,屏幕阅读器也未必读;label是控件的正式名称,点击能聚焦输入框,是无障碍和可理解性的基础。- 任何人可以绕过页面直接构造 HTTP 请求,浏览器校验根本不会执行。所以数据是否合法、是否安全,必须由后端再校验一次。
下一步
页面现在能收集用户输入了,但样子还停留在浏览器默认风格。下一节开始学 CSS 引入与选择器,给表单、文字和整个页面装上第一层外观。