←返回笔记列表
01 · NOTES / WebGIS 开发

第 03 节 · 表单与输入控件

2026 年 9 月 7 日

第 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 的校验气泡分别触发过
  • 底图三选一和主题二选一互不干扰,并能解释分组靠什么实现

自测题

  1. id 和 name 分别干什么?忘掉哪一个会导致数据提交不上去?
  2. 一组单选按钮靠什么形成“一组”?
  3. 表单里的按钮默认 type 是什么?忘记设置会产生什么后果?
  4. placeholder 和 label 有什么区别?为什么不能用前者替代后者?
  5. 为什么说前端校验只是体验优化?

参考答案

  1. id 是页面内的唯一身份,供 label 和 JS 使用;name 是提交数据的键名。忘记 name,这个字段根本不会出现在请求里。
  2. 相同的 name 属性值。同一组单选用同一个名字,浏览器才把它们当作互斥的一组。
  3. 默认是 submit。忘记设置会把普通按钮当成提交按钮,点击后触发表单提交,页面跳转或刷新。
  4. placeholder 是输入前的灰色提示,输入后就消失,不参与提交,屏幕阅读器也未必读;label 是控件的正式名称,点击能聚焦输入框,是无障碍和可理解性的基础。
  5. 任何人可以绕过页面直接构造 HTTP 请求,浏览器校验根本不会执行。所以数据是否合法、是否安全,必须由后端再校验一次。

下一步

页面现在能收集用户输入了,但样子还停留在浏览器默认风格。下一节开始学 CSS 引入与选择器,给表单、文字和整个页面装上第一层外观。

TAGSweb