当你为网络构建表单时,让语义、可访问性和风格正确是一项非常重要的工作。如果你能把这些都做好,你就做得很好了。然而,我们仍然可以做一些事情来使填写表格的人生活得更好。
在这篇文章中,我们将看看HTML表单用户体验(UX)的一些该做和不该做的事情。如果你想复习一下上面提到的前几个步骤,可以看看本系列的其他文章。
内容
要求最少的信息量
作为一个互联网用户,我可以根据经验说,在一个表格中输入超过必要的数据是很烦人的。因此,如果你真的只需要一个电子邮件,可以考虑不要求输入名字、姓氏和电话号码。通过创建输入较少的表单,你将改善用户体验。一些研究甚至表明,较小的表格有较高的转换率。这对你来说是一个胜利。另外,减少你收集的数据有可能减少你的隐私问题,尽管这在很大程度上取决于数据。
保持简单
在表单设计中发挥你的创造力是很诱人的。然而,这很容易过火,使事情变得混乱。通过坚持使用标准输入类型的简单设计,你正在创造一种更有凝聚力的体验,不仅是在你的网站上,而且在整个互联网上。这意味着用户不太可能被一些花哨新颖的输入法所迷惑。坚持使用经典。请记住,像复选框这样的选择输入(允许多个选择项目)通常使用盒式输入,而收音机(只允许一个选择)则使用圆圈。
语义学对a11y和用户体验有好处
我在之前的文章中更详细地介绍了语义学,但简单地说,选择正确的输入类型可以在很多层面上改善体验:语义学、可访问性和用户体验。人们已经习惯了整个网络上的输入方式,所以我们可以利用这一点,对相同的事物使用相同的输入。更不用说通过使用正确的输入,我们可以免费获得很多东西,比如键盘导航支持和验证。
把国家选择器放在城市/州之前
这对任何在表单中添加地域性的人来说都是一个简单的规则。如果你要询问用户的国家,请把它放在城市和州的字段之前。通常情况下,城市和州将根据国家的情况来填充。因此,如果你的国家选择默认为美国,而用户住在墨西哥的瓦哈卡,他们将需要跳过城市和州的字段,选择墨西哥这个国家,然后在列表更新后再去填入他们的城市和州。通过把国家放在前面,你可以保持表单的流程,这对于使用键盘导航的用户来说特别好。
将长的表格分页
这与我的第一点有关,在理想情况下,你不会有太多的数据。然而,在某些情况下,这是没办法的事。在这些情况下,给表格分页可能是有意义的,这样信息就不会被淹没了。如果你选择分页,我最好的建议是向用户展示某种关于他们在表单中的进展的用户界面,并提供删除分页和显示整个表单的选项。
一般功能
防止浏览器刷新/导航
你是否曾经在填写一个长的表格时,不小心刷新了页面,导致你的工作全部丢失?这是最糟糕的。幸运的是,浏览器为我们提供了 [beforeunload](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event)事件,我们可以用它来通知用户他们即将失去任何未保存的工作。
我们可以设置一个变量来跟踪表单中是否有任何未保存的更改,并且我们可以给beforeunload 事件附加一个处理程序,如果有任何更改,它将阻止浏览器导航。
// You'll need some variable for tracking the status. We'll call it hasChanges here.
window.addEventListener("beforeunload", (event) {
if (!hasChanges) return;
event.preventDefault();
event.returnValue = "";
})
form.addEventListener('change', () => {
hasChanges = true;
});
form.addEventListener('submit', () => {
hasChanges = false;
})
这个片段的要点是,我们正在跟踪某个名为hasChanges 的变量。如果在beforeunload 事件发生时,hasChanges 是false ,我们就可以让浏览器顺利导航。如果hasChanges 是true ,浏览器就会提示用户,让他们知道他们有未保存的更改,并询问他们是否要继续离开或留在该页面上。最后,我们在表单中添加适当的事件处理程序来更新hasChanges 。
对于hasChanges 这个变量,你的实现可能略有不同。例如,如果你使用的是带有一些状态管理的JavaScript框架,如果你正在创建一个单页应用程序,那么这个解决方案就不太合适了,因为beforeunload 事件不会在单页应用程序导航时触发。
存储未保存的更改
沿着上一点的思路,有时我们会不小心失去我们在一个长表格上的所有工作。幸运的是,我们可以通过利用浏览器的功能来避免给用户带来这种痛苦,例如 [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).比方说,我们想在变化事件发生的任何时候存储表单中的所有数据。我们可以使用 [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)来捕获表单和它的所有当前值,然后将数据作为一个JSON 字符串存储在sessionStorage 。
JSON
const form = document.querySelector('form')
form.addEventListener('change', event => {
const formData = new FormData(form);
sessionStorage.setItem('your-identifier', JSON.stringify(formData));
});
保存好数据后,用户可以随意刷新,数据也不会丢失。下一步是在页面加载时检查localStorage ,看看我们是否有任何先前保存的数据可以预先填充到表单中。如果有的话,我们可以把字符串解析成一个对象,然后在每个键/值对上循环,把保存的数据添加到各自的输入中。对于不同的输入类型,这略有不同。
JSON
const previouslySavedData = sessionStorage.getItem('form-data');
if (previouslySavedData) {
const inputValues = JSON.parse(savedData);
for(const [name, value] of Object.entries(inputValues)) {
const input = form.querySelector(`input[name=${name}]`);
switch(input.type) {
case 'checkbox':
input.checked = !!value;
break;
// other input type logic
default:
input.value = value;
}
}
}
最后要做的是确保在表单提交后,我们要清理任何先前保存的数据。这也是我们使用sessionStorage 而不是localStorage 的部分原因。我们希望我们保存的数据在某种程度上是不稳定的。
JSON
form.addEventListener('submit', () => {
sessionStorage.removeItem('form-data');
});
关于这个功能,最后要说的是,它并不适合所有的数据。任何私人或敏感数据都应该被排除在任何localStorage 持久性之外。而且有些输入类型根本无法使用。例如,没有办法持久化一个文件输入。然而,在理解了这些注意事项之后,它可以成为一个伟大的功能,可以添加到几乎所有的表单中,特别是长的表单。
不要阻止复制/粘贴
我最近经历的最恼人的事情之一是在国税局的网站上。他们要求我提供我的银行账户号码和银行路由号码。这些都不是简短的数字--我们说的是15个字符左右。在大多数网站上,这是没有问题的,我从我的银行网站上复制这些数字并把它们粘贴到输入框中。然而,在国税局的网站上,他们选择禁用粘贴输入,这意味着我不得不手动填写每个数字的细节......两次。我不知道他们为什么这样做,但这对用户来说是非常令人沮丧的,而且实际上增加了出错的可能性。请不要这样做。
输入功能
输入模式
如果你以前没有听说过 [inputmode](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode) inputmode 是一个HTML输入属性,让你告诉浏览器输入格式。这一点可能不是很明显,如果你是在台式电脑上,那么你不会注意到它,但对于移动用户来说,它却有很大的不同。通过选择不同的输入模式,浏览器将向用户展示不同的虚拟键盘来输入他们的数据。
你可以通过简单地添加不同的输入模式来大大改善移动用户填写表单的用户体验。例如,如果你要求输入数字数据,如信用卡号码,你可以将inputmode ,numeric 。这使用户更容易添加数字。对于电子邮件也是如此,inputmode=email 。
inputmode 的可用值是none,text,tel,url,email,numeric,decimal, 和search 。关于更多的例子,请查看inputmodes.com(最好是在移动设备上)。
自动完成
伴随着inputmode ,该 [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) 属性是一个内置功能,可以大大改善你的表单的用户体验。许多网站使用表单来要求用户提供相同的信息:电子邮件、地址、电话、信用卡等。而浏览器中内置的一个非常好的功能是,用户可以保存自己的信息,这样就可以在不同的表单和网站上自动完成。autocomplete ,让我们可以利用这个功能。
自动完成属性对任何文本或数字输入以及<textarea>,<select>, 和<form> 元素都有效。有太多的可用值,我无法在这里列出,但一些突出的值是:current-password,one-time-code,street-address,cc-number (和其他各种信用卡选项),和tel 。
提供这些选项可以为许多用户带来更美好的体验。而且不用担心这是个安全问题,因为这些信息只存在于用户的机器上,而且他们必须允许他们的浏览器实现这些信息。
自动对焦
我将提到的最后一个内置属性是 [autofocus](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefautofocus).通过将其添加到输入中,浏览器会将焦点放在输入、选择或文本区域(Chrome也支持在<button> 、<a> 、以及带有tabindex 的元素上使用它)。如果当前页面的主要内容是填写表格,这可能超级有用。例如,如果你打开duckduckgo.com,你会发现搜索输入已经被聚焦了。这不是默认的行为,但他们已经添加了它。这很好。
不过,在这里要提醒一下。不是每个表单都适合autofocus 。把自动聚焦放在一个元素上会滚动到该元素。因此,如果页面上有其他内容,我们可能会滚动过所有这些内容。这对依赖读屏器等辅助技术的用户来说是一种特别刺耳的体验。请只在该功能能真正改善所有用户的体验时使用。
自动扩展的文本区
一个非常小的功能,但我很欣赏的是一个textarea ,它可以自动扩展以匹配其中的内容。这样你就不必处理那些巨大的文本区,或者那些太小而需要滚动条来处理的文本区。这可能不是每个用例的正确功能,但它确实可以为一些表单增加一些光彩。这里有一个天真的实现。
JSON
textarea.addEventListener('input', () => {
textarea.style.height = "";
textarea.style.height = Math.min(textarea.scrollHeight, 300) + "px";
});
我称其为天真的实现,因为根据我的经验,由于不同的网站有不同的CSS规则应用于文本区域,所以很难得到一个放之四海而皆准的解决方案。有时是受padding 或border-width 的影响,有时则是因为box-sizing 属性不同。在任何情况下,你都可以把这个作为一个起点,当然,你也可以伸手去找一个库。
禁用数字输入的滚动事件
如果你不熟悉,在数字输入上有一个浏览器功能,允许你用鼠标滚轮来增加或减少数值。如果你需要快速改变数值而不想打字,这可能是一个很好的功能。然而,这个功能也可能导致错误,因为在需要滚动的长页面上,用户有时会意外地减少他们的输入,而他们的目的是向下滚动页面。有一个足够简单的解决方案。
JSON
<input type="number" onwheel="return false;" />
通过添加这个 [onwheel](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event)事件处理程序,我们基本上是在告诉浏览器忽略该事件(但它仍然会触发任何附加的wheel 事件)。因此,如果我们正在处理像地址、邮编、电话号码、社会保险、信用卡或任何其他明显不需要增量或减量的数字,我们可以使用这个方便的片段。然而,在这些情况下,我可能会建议使用text 输入来代替。
验证
验证是指你获取表单数据并确保它与你所寻找的格式相匹配。例如,如果你想让某人在表单中提交一个电子邮件,你需要验证它是否包含一个@ 符号。有很多不同类型的验证,也有很多方法。有些验证发生在客户端,有些发生在服务器端。我们来看看一些 "应该 "和 "不应该"。
延迟验证以模糊或提交事件
有了HTML5,在你的表单中添加一些客户端验证就很容易了。你可以决定用一些JavaScript来加强它,但你选择什么时候验证输入是很重要的。
假设你有一个函数,接受一个输入的DOM节点,检查其 [ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState),并在它是否有效的情况下切换一个类。
JSON
function validate(input) {
if (input.validity.valid) {
input.classList.remove('invalid')
} else {
input.classList.add('invalid')
}
}
你必须选择何时运行这个函数。它可能是在用户点击输入、按下按键、离开输入或提交表单的任何时候。我的建议是为以下两种情况保留验证事件 [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event)事件(当一个输入失去焦点时)或在表单的 [submit](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event)事件。在最初的焦点上验证似乎不合适,而在按键上验证也会很烦人。这就像有人试图在你完成评论之前纠正你一样。
在大多数情况下,我喜欢把我的验证逻辑与提交事件联系起来。我认为这可以简化事情,并且在我想要一些服务器端验证逻辑的情况下保持一个更有凝聚力的体验。这就是说,blur 事件也是一个非常方便的验证地方。
不要隐藏验证标准
另一个有用的,甚至是不明显的提示是,预先清楚地告诉用户是什么让一个输入有效或无效的。通过分享这些信息,他们已经知道他们的新密码需要8个字符长,包含大写和小写字母,并包括特殊字符。他们不需要通过尝试一个密码的步骤,而被告知需要选择另一个。
我建议有两种方式来实现这一点。如果是基本的格式,你也许可以使用placeholder 属性来实现。对于更复杂的东西,我建议把要求放在纯文本中,紧挨着输入的下面,并在输入上包括一个aria-labelledby 属性,这样,这些要求也会传递给辅助技术用户。
一次性送回所有的服务器验证错误
用户在填写表格时的另一个非常恼人的经历是不得不多次重新提交同一个表格,因为有些数据是无效的。这可能是因为服务器一次只验证一个字段并立即返回错误,或者是因为一个输入有多个验证标准,但服务器一遇到第一个验证标准就返回验证错误,而不是捕捉每一个错误。
举个例子,假设我有一个注册表,需要我的电子邮件和一个至少有八个字符的密码,至少有一个字母和至少一个数字。最坏的情况是,如果我不了解情况,我可能需要多次重新提交表格。
- 错误,因为我没有包括一个电子邮件
- 错误,因为我的密码太短
- 错误,因为我的密码需要包括字母
- 错误,因为我的密码需要包括数字
- 成功!
作为编写表单的开发者,我们并不总是能够控制后台的逻辑,但是如果我们能够控制,我们应该尽量把所有的错误作为一条信息提供回来。"第一个输入必须是一个电子邮件。密码必须是8个字符。只能包含字母和数字。密码必须包含1个字母和1个数字。"或类似的话。然后,用户可以一次解决所有的错误并重新提交。
提交
用JavaScript提交
不管你对JavaScript在我们生活中的每一个部分的爆炸性增长有什么看法,不可否认的是,它是一个有用的工具,可以使用户体验更好。表格就是一个完美的例子。与其等待浏览器提交表单,我们可以使用JavaScript,避免页面重载。
要做到这一点,我们要在事件中添加一个事件监听器。 [submit](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event)事件,通过将表单(event.target)传入 "事件监听器 "来捕获表单的输入值。 [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData),并将数据发送到目标URL (form.action),同时使用 [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)和 [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
JavaScript
function submitForm(event) {
const form = event.target
const formData = new FormData(form)
fetch(form.action, {
method: form.method,
body: new URLSearchParams(formData)
})
event.preventDefault()
}
document.querySelector('form').addEventListener('submit', submitForm)
处理程序末尾的event.preventDefault() ,防止浏览器通过HTTP请求提交事件的默认行为。这将导致页面的重新加载,而且不是一个好的体验。一个关键的问题是,我们把这个方法放在最后,只是为了防止我们在处理程序的某个地方出现异常,我们的表单仍然会退回到HTTP请求,表单仍然会被提交。
包括状态指示器
这个提示与前面的提示密切相关。如果我们要用JavaScript来提交表单,我们需要更新用户的提交状态。例如,当用户点击提交按钮时,应该有某种指示(最好是视觉_和_非视觉的),表明请求已经发送。事实上,我们有四种状态可以说明。
- 在请求被发送之前(这里可能没有什么特别的需要
- 请求待定
- 收到成功的响应
- 收到失败的响应
有太多的可能性,我无法告诉你在你的情况下你到底需要什么,但重点是你要记得说明所有这些。不要让用户想知道发送的请求是否有错误。(那是一个让他们乱按提交按钮的快速方法)。)不要假设每个请求都会成功。告诉他们有一个错误,如果可能的话,如何解决这个问题。当他们的请求成功时,给他们一些确认。
滚动错误
在你的表单出现的情况下,最好让用户知道到底是什么地方出错了(就像我们上面看到的那样),以及在哪里出错。特别是在长滚动的页面上,你的用户有可能试图提交一个有某种错误的表单,即使你把输入的颜色染成红色并添加一些验证错误信息,他们也可能看不到,因为它不在他们所在的屏幕的同一个部分。
再一次,JavaScript可以在这里帮助我们,搜索表单中第一个无效的输入元素并关注它。浏览器会自动滚动到任何收到焦点的元素,所以只需很少的代码,你就可以提供一个更好的体验。
JavaScript
function focusInvalidInputs(event) => {
const invalidInput = event.target.querySelector(':invalid')
invalidInput.focus()
event.preventDefault()
}
document.querySelector('form').addEventListener('submit', focusInvalidInputs)
用户体验是一个非常主观的东西,这个列表并不打算完全完整,但我希望它能为你提供一些概念和模式来改善你的表单。