对一次性代码使用单一输入

495 阅读2分钟

前一天晚上,我在尝试创建单输入一次性代码时获得了一些乐趣:

一次性代码一次性代码是一个有效值autocomplete,通过几行 JS,您可以通过文本消息 (sms) 填写该字段。

不过,更常见的是,它被称为OTP

来自维基百科:

一次性密码 (OTP),也称为一次性 PIN、一次性授权码 (OTAC) 或动态密码,是一种在计算机系统或其他系统上仅对一次登录会话或交易有效的密码数字设备。


回到我的例子。

它使用非常简单的标记:

<input
  type="text"
  autocomplete="one-time-code"
  inputmode="numeric"
  maxlength="6"
  pattern="\d{6}"
>

CSS 有点复杂:

:where([autocomplete=one-time-code]) {
  --otp-digits: 6; /* length */
  --otc-ls: 2ch;
  --otc-gap: 1.25;
  /* private consts */
  --_otp-bgsz: calc(var(--otc-ls) + 1ch);

  all: unset;
  background: linear-gradient(90deg, 
    var(--otc-bg, #EEE) calc(var(--otc-gap) * var(--otc-ls)),
    transparent 0
  ) 0 0 / var(--_otp-bgsz) 100%;
  caret-color: var(--otc-cc, #333);
  clip-path: inset(0% calc(var(--otc-ls) / 2) 0% 0%);
  font-family: ui-monospace, monospace;
  font-size: var(--otc-fz, 2.5em);
  inline-size: calc(var(--otc-digits) * var(--_otp-bgsz));
  letter-spacing: var(--otc-ls);
  padding-block: var(--otc-pb, 1ch);
  padding-inline-start: calc(((var(--otc-ls) - 1ch) / 2) * var(--otc-gap));
}

它是一堆模拟 6 个字段(来自属性--otc-digits)的东西,而实际上它只是一个<input>. “字段”之间的间距是由于letter-spacing,灰色“框”来自linear-gradient.

它必须使用等宽字体,因此作品的神奇价值1ch——同样适用于letter-spacing1ch等于零的宽度。


但为什么?

您以前创建过 OTP 组件吗?

我正在编写“组件”,因为它通常是一个<fieldset>带有六个<input>s 和一堆 JavaScript 来检测您何时进入或离开字段等。

当您从Web OTP API填写字段时,您需要拆分值,并填写六个字段而不是一个。

使用单个输入就简单得多 :

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);

突出显示当前“字段”

单输入 OTP 并不完美。当您从“字段”移动到“字段”时,如果插入符是一个块,那将是更好的用户体验:

.selector {
  caret-shape: block;
}

不幸的是,还没有浏览器支持caret-shape


另一种方法是添加另一个 background-gradient,但不重复该模式:

图片描述

并通过将 size-property — --_otp-bgsz— 与 digit-number 相乘来定位它,--_otp-digit作为自定义属性:

.selector {
  background-position: 
    calc(var(--_otp-digit, 0) * var(--_otp-bgsz)) 0;
}

这并不完美,因为我们需要将数字放在 CSS 自定义属性中,然后使用 JavaScript更新它:

input.addEventListener('input', () => 
  input.style.setProperty('--_otp-digit', 
  input.selectionStart)
)

这可以用更简单的方式完成吗?其他建议?请使用评论!