类似 [ ][ ][ ][ ][ ][ ] 的短数字输入框组件,在网络中经常遇到,例如京东支付、支付宝支付的密码等。
我们应该如何开发一个功能和体验完整的固定长度输入框组件?
具备的功能
至少满足以下条件才是我认为体验完善的数字输入框,我至今没有在国内网站上遇到过全部符合的网站。
- 合理的结构和样式、动效设计
- 一格只能输入一个数字
- 快速键盘连续输入时不能出现错误
- 输入正确内容后 跳到下一格
- 删除内容后 跳回上一格
- 点击其中一格时,内容应该显示为空。以做到不干扰用户输入(如 [1|] 、[|1] 这样的输入体验让用户不知道输到前面还是输到后面)
- 长按删除按钮时,应该可以连续删除而不是只删除一格
可以按需具备以下功能
- 方向键控制跳格
- 当粘贴内容时,应该识别出里面符合条件的数字并帮用户填充(验证码短信全文复制时)
1. 合理的结构
样式和动效不必废话,通常取决于产品或设计或自身审美。
关于结构我是通过 6 个 Input 标签来进行实现的。
我看到过有人通过一个隐藏的 Input 上面放 6 个无法获得焦点的框,我认为这样的设计想法很好,但是遇到的问题会很多,(例如产品要求每 3 个框中间多留一些空间)。
所以在页面结构设计时,不仅限于这个密码输入框,我们需要充分考虑产品和设计师未来对这个组件可能的更改和扩展。
See the Pen 合理的密码输入框-1 by Hoyt (@hoythan) on CodePen.0
有几个 Input 标签属性是需要我们留意的,合理的使用能减少不必要的异常或兼容问题:
type
通常我们会使用 text
,但在移动端会弹出全键盘,用户体验不佳。
如果使用 number
,虽然弹出数字键盘,但 PC 端会有上下增减按钮以及 maxlength
失效的问题。
建议使用 type="tel"
类型,既有数字键盘,又可以限制长度还没有浏览器自带的控件干扰。
maxlength
限制输入框最多允许输入的内容长度 maxlength="1"
。不要相信它永远稳定,你还是需要通过 JS 进行控制,但设置也无妨。
autocorrect
这是移动端的 “自动更正” 功能,我们要做的输入框显然是不需要系统帮用户做更正的,关闭。
autocomplete
这是移动端的 “自动完成输入” 功能,关闭。
autocapitalize
移动端 “自动大小写”,遇到需要全部大写的情况,建议通过 JS 强制转换而不是信赖这个属性,关闭。
spellcheck
“拼写检查”,显然这是一个不需要的功能,关闭。
tabindex
通常你不必理会这个参数,它确保用户按下 tab
键时可以跳转到正确的位置
2. 所需事件和流程
Focus
输入框获得焦点的事件,在获得焦点时将当前输入框内容保存到当前 Element 对象属性中,例如 el.prevValue
,并清空当前输入框。
在移除内容后,建议设置
placeholder
为被清空的内容,以告知用户会覆盖这个内容而不是显示空白。
Blur
通过 Focus 保存的内容,在失去焦点时,如果用户没有输入且el.prevValue
有内容则将内容填充回来。如上图所示。
KeyDown
在键盘按下时判断用户是否点击了 删除键、退格键、键盘方向左右键 并做如下相应处理。
删除键
KeyNames: [‘Backspace’, ‘Delete’, ‘Del’]
当监听到删除键时,清空当前 Input 的 Value 值(同时清空上面提到的el.prevValue
和placeholder
)
并判断如果不是第一个输入框则往前跳一格
方向键
KeyNames: [‘Left’, ‘ArrowLeft’,’Right’, ‘ArrowRight’]
跳格和其他不同的是,当跳转到末尾或开头,它可以继续循环跳格。
KeyUp
在用户输入内容后,过滤不需要的内容,例如用户输入的字母等,如果用户输入的是数字,则将焦点移动到下一个输入框(如果有)。否则焦点位置不变,允许用户继续输入它。
在你快速输入内容时,一定要确保输入的内容不会丢失。
而实际情况是,Input 的 Value 并不会出现你想要的内容,因为 Focus 的延迟会导致快速的输入内容被忽略。
你要做的是在 KeyUp 时去判断用户按下的是哪个键,并将内容放到 value 中。
// 过滤数字 const num = String.replace(/[^0-9]/i, ""); // 快速输入问题解决, 将接收到输入事件,但此时 value 没有值。 const char = event.key if (char.length === 1) val = char
Paste
黏贴事件处理,过滤出需要的内容,并黏贴到输入框中。这里需要注意的是,一定要判断是否为 6 个长度的数字(取决于输入框数量),不论在哪个框中进行黏贴,一律从第一个框开始填充数字。
即用户复制 6 个数字的验证码,允许在任意框中进行黏贴。用户复制的内容不符合 6 个数字验证码的,应该不做任何填充而不是填充一部分。
/** * 获取粘贴的内容 * @https://developer.mozilla.org/zh-CN/docs/Web/Events/paste */ const paste = (event.clipboardData || window.clipboardData).getData('text') /** * 正则匹配指定长度的数字 */ String.match(/(?<!\d)\d{6}(?!\d)/i, "") // OR ` ${String}`.match(/[^\d](?<code>\d{6})(?!\d)/i) { "您的验证码为:888888 ,客服电话 1123456":888888, "客服电话 1123456,您的验证码为:888888":888888, "888888 是您的验证码,请注意查收,客服电话:777777":888888, "我们的电话是:1234567 888888是您的验证码":888888, }
密码不推荐支持黏贴的形式。
第一个正则写法看起来很直接,但其 Lookbehind 兼容性不尽人意。
考虑很久后还是采用了第二个写法,虽需在前面添加一个空字符串做处理,但兼容性更优异。
Ps: 如果你有更好的正则可以符合以上 4 条测试条件,欢迎联系我。
完整示例
只要理解以上内容,代码编写没有什么难度,大可按照自己的风格编写,以下代码仅供参考,使用 Vue.js 编写。具备 以上 “具备的功能” 所提到的所有功能
See the Pen 合理的密码输入框-2 by Hoyt (@hoythan) on CodePen.0