DOOFOX BLOG

7月 08 2019 Category:

功能完善的 密码/验证码 输入框


类似 [ ][ ][ ][ ][ ][ ] 的短数字输入框组件,在网络中经常遇到,例如京东支付、支付宝支付的密码等。
我们应该如何开发一个功能和体验完整的固定长度输入框组件?

具备的功能

至少满足以下条件才是我认为体验完善的数字输入框,我至今没有在国内网站上遇到过全部符合的网站。

可以按需具备以下功能

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.prevValueplaceholder
并判断如果不是第一个输入框则往前跳一格

方向键

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