如何在 HTML 中制作表单
表单就是一个 <form> 元素,里面包含字段和一个提交按钮。每个字段都需要一个 name:它是发送值时使用的键。填写表单,然后点击 Send。预览不会把任何东西发送到任何地方;它会准确显示浏览器本来会发送的内容。
各个组成部分:
action是数据要发送到的 URL,method是发送方式(这里是post)。- 每个字段都有
name。它的值就是读者输入的内容。 <label>在屏幕上为每个字段命名,也为屏幕阅读器命名(参见 HTML label)。required和type="email"让浏览器在发送前检查字段。试试姓名留空发送,或者把邮箱填成abc。- 提交按钮会发送表单。在文本字段中按 Enter 键也会。
action 和 method:数据发到哪里、怎么发
| 属性 | 取值 | 默认值 |
|---|---|---|
action | 任意 URL | 当前页面的 URL |
method | get、post、dialog | get |
enctype | application/x-www-form-urlencoded、multipart/form-data、text/plain | application/x-www-form-urlencoded |
使用 method="get" 时,字段会变成 URL 的查询字符串:带 name="q" 的搜索表单会跳转到 /search?q=blue+shoes。这对搜索和筛选来说正合适,因为结果可以收藏为书签并分享。使用 method="post" 时,字段放在请求体中,不会出现在地址栏或历史记录里。凡是登录、创建、修改或删除东西的操作,以及涉及密码的表单,都要用 POST。
带文件上传(<input type="file">)的表单需要 method="post" 和 enctype="multipart/form-data",否则只会发送文件名。method="dialog" 用于 <dialog> 里的表单:提交时会关闭对话框,而不是发送请求。
会发送什么
只有带 name 的字段才会被发送,而且前提是它们没有被 disabled。点击 Submit,把字段和表单发送的内容对比一下:
表单发送了 city、zip、source 和 action。没有名称的字段、被禁用的字段和未勾选的复选框都被排除了。隐藏输入框会被发送,但永远不会显示;提交按钮会发送它自己的 name 和 value,因为提交时用的就是它。
用 fieldset 和 legend 给字段分组
<fieldset> 在相关字段周围画一个框,<legend> 是它的标题。焦点进入这一组时,屏幕阅读器会朗读 legend,正因如此,下面这样的一组单选按钮才能被理解:“Delivery, Standard, radio button”。
fieldset 上的 disabled 会一次性禁用其中的所有字段,它们都不会被发送。去掉它,礼品相关的字段就可以使用了。
内置验证:required、pattern 等
浏览器会在提交前检查这些属性,并在第一个未通过的字段上显示提示:
| 属性 | 检查内容 |
|---|---|
required | 字段不为空(复选框已勾选) |
type="email"、type="url" | 值看起来像邮箱地址或 URL |
minlength、maxlength | 文本长度 |
min、max、step | 数字和日期的范围 |
pattern | 整个值匹配一个正则表达式 |
:invalid 和 :valid 根据字段状态设置样式。:user-invalid 通常更好:它只在读者修改过字段并离开、或者尝试提交之后才生效,所以空表单打开时不会满屏飘红。
pattern 有三点容易让人踩坑。它必须匹配整个值(不需要 ^ 或 $)。它只在字段不为空时才检查,所以要搭配 required 使用。另外浏览器的错误提示很笼统,所以要在字段旁边用可见的文字说明格式。Chrome 和 Firefox 会把 title 的文字加进提示里,但很容易被忽略。
要关闭这些检查,比如在测试服务器端验证时,给 <form> 加上 novalidate。单个提交按钮可以用 formnovalidate 跳过检查。
浏览器验证是为了方便读者。任何人都可以修改页面,或者绕过它直接发送请求,所以服务器必须重新检查每一个值。
用 setCustomValidity 自定义提示
当某条规则无法用属性表达时,比如“两次输入的密码必须一致”,就用 JavaScript 设置提示。自定义提示不为空的字段是无效的;设为空字符串就会让它重新变为有效:
重复输入的字段没有 name,所以密码只会发送一份。
用 JavaScript 处理提交
要在不离开页面的情况下发送表单,就监听 submit,调用 preventDefault(),再用 FormData 读取字段。监听 submit 而不是按钮的 click,可以捕获所有提交方式,包括按 Enter 键,而且只会在内置验证通过之后才运行:
Object.fromEntries(new FormData(form)) 把字段转换成一个普通对象。对于可能有多个值的字段(同名的多个复选框、多选的 select),请改用 formData.getAll('name'),因为对象只保留最后一个值。
常见错误
- 字段没有
name。 它们会被悄无声息地排除在提交的数据之外。 - 用占位符代替标签。 读者一开始输入,提示就消失了。请用
<label>。 - 没有
type的<button>被用来做提交以外的事情。在表单里,它默认就是提交按钮。 - 表单里嵌套表单。 嵌套表单是无效的;浏览器会忽略内层的
<form>标签。 - 监听提交按钮的
click,而不是表单的submit。这样会漏掉用 Enter 键提交的情况。 - 在服务器端信任浏览器验证。 在服务器上要再验证一次。
- 用 GET 发送密码。 密码会出现在 URL、历史记录和服务器日志里。
常见问题
HTML 中怎么创建表单?
用 <form action="/signup" method="post"> 包住各个字段,给每个字段一个 name 和一个 <label>,最后放一个 <button type="submit">。表单提交时,浏览器会把每一对 name=value 发送到 action 指定的 URL。
表单中的 GET 和 POST 有什么区别?
method="get"(默认值)把字段放进 URL,例如 /search?q=shoes,适合需要能收藏为书签的搜索和筛选。method="post" 把字段放在请求体中发送,登录、注册以及任何会修改数据的操作都应该用它。
为什么我的表单字段没有被提交?
它没有 name 属性,或者它是 disabled 的。只有带名称且未禁用的字段才会被发送。未勾选的复选框或单选按钮也会被完全忽略。
fieldset 和 legend 有什么用?
<fieldset> 把相关字段分成一组,<legend> 是这一组的标题。焦点进入这一组时,屏幕阅读器会朗读 legend,这正是一组单选按钮能被理解的原因;给 fieldset 加上 disabled 会禁用其中的所有字段。
不用 JavaScript 怎么验证 HTML 表单?
使用属性:required、type="email"、minlength、maxlength、min、max 和 pattern。浏览器会阻止提交,并在第一个无效字段上显示提示。服务器端也一定要再验证一次,因为这些检查在读者的浏览器中运行,可以被绕过。
怎样阻止表单提交后刷新页面?
监听表单的 submit 事件并调用 event.preventDefault(),然后自己用 fetch 发送数据:form.addEventListener('submit', (e) => { e.preventDefault(); fetch(form.action, { method: 'POST', body: new FormData(form) }); })。