Vue实现验证码输入交互

最近做一个H5的页面,里面有个输入验证码交互,就是移动端比较常见的那种验证码输入交互。就是那种,对,就是那种,一个数字一个下划线,移动端非常常见的那种验证码交互。实现过程中主要参考了美团外卖安卓端的具体交互。

文章首发于我的 个人博客

应用到项目中的效果如下。

一般操作.gif
粘贴效果

方案选择

方案1:调整文字的间距

设置 input 的 letter-spacing 属性,我们就可以让验证码之间有足够大的空隙,然后再把底线改为有间隔的多个线段貌似就可以了。

然而,这里会有一个问题。就是光标总是会在数字的左边,而我们希望的是输入后的数字的中心位于原来光标的位置。最终我放弃了这个方案。

显然,这个方案并不合适。

方案2:使用多个 input

这就是我使用的方式,也是接下来我要详细讲解的方案。主要原理是:使用多个 input 元素,每个 input 只能输入一个数字。当通过 input 事件监测到字符输入时,自动将焦点对焦到下一个 input 元素。

当然我们还要实现点击任何一个输入框时,将焦点移动到第一个value为空的input上。另外,点击退格键时,也要进行焦点的改变。

测试后后发现,焦点的移动,不会导致移动端键盘的收起。最终我就决定使用这个方案了。

代码实现

在线示例:https://codepen.io/F-star/pen/dyyeZaN

HTML:

<div id="app">
  <div class="captcha">
    <input v-for="(c, index) in ct" :key="index"
      type="number" v-model="ct[index]" ref="input" 
      :style="{borderBottomColor: index <= cIndex ? '#333' : ''}"
      @input="e => {onInput(e.target.value, index)}" 
      @keydown.delete="e=>{onKeydown(e.target.value, index)}"
      @focus="onFocus"
      :disabled="loading"
      >
  </div>
  <p>{{msg}}</p>
</div>

CSS:

.captcha {
  display: flex;
  justify-content: center;
  margin-top: 40px;
}
input {
  margin-right: 20px;
  width: 45px;
  text-align: center;
  border: none;
  border-bottom: 1px solid #eee;
  font-size: 24px;
  outline: none;
}
input:last-of-type {
  margin-right: 0;
}
input:disabled {
  color: #000;
  background-color: #fff;
}
.msg {
  text-align: center;
}

JS:

var Main = {
  data() {
    return {
      ct: ['', '', '', '', '', ''],
      loading: false,
      msg: '',
    }
  },
  computed: {
    ctSize() {
      return this.ct.length;
    },
    cIndex() {
      let i = this.ct.findIndex(item => item === '');
      i = (i + this.ctSize) % this.ctSize;
      return i;
    },
    lastCode() {
      return this.ct[this.ctSize - 1];
    }
  },
  watch: {
    cIndex() {
      this.resetCaret();
    },
    lastCode(val) {
      if (val) {
        console.log('this.ctSize', this.ctSize)
        this.$refs.input[this.ctSize - 1].blur();
        this.sendCaptcha();
      }
    }
  },
  mounted() {
    this.resetCaret();
  },
  methods: {
    onInput(val, index) {
      this.msg = ''
      val = val.replace(/\s/g, '');
      if (index == this.ctSize - 1) {
        this.ct[this.ctSize - 1] = val[0];   // 最后一个码,只允许输入一个字符。
      } else if(val.length > 1) {
        let i = index;
        for (i = index; i < this.ctSize && i - index < val.length; i++) {
          this.ct[i] = val[i];
        }
        this.resetCaret();
      }
    },
    // 重置光标位置。
    resetCaret() {
      this.$refs.input[this.ctSize-1].focus();
    },
    onFocus() {
      // 监听 focus 事件,将光标重定位到“第一个空白符的位置”。
      let index = this.ct.findIndex(item => item === '');
      index = (index + this.ctSize) % this.ctSize;
      console.log(this.$refs.input)
      this.$refs.input[index].focus();
    },
    onKeydown(val, index) {
      if (val === '') {
        // 删除上一个input里的值,并对其focus。
        if (index > 0) {
          this.ct[index - 1] = '';
          this.$refs.input[index - 1].focus();
        }
      }
    },
    sendCaptcha() {
      console.log();
      this.msg = `发送验证码到服务器:${this.ct.join('')}`;
      // 此时无法操作 input。。
      this.loading = true;
      setTimeout(() => {
        this.msg = ('验证码错误')
        this.loading = false;
        this.$nextTick(() => {
          this.reset();
        })
      }, 3000)
    },

    reset() {
      // 重置。一般是验证码错误时触发。
      this.ct = this.ct.map(item => '');
      this.resetCaret();
    }
  }
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

原理

创建多个 input 元素,对这些 input 都绑定 focus 事件。一旦触发该事件,我们会把焦点移动到从左往右第一个 value 为空字符的 input 上。所以在初始状态时,点击最右边的 input,光标还是会跑到最左边的 input。

然后我们给这些 input 绑定 input 事件,监听输入字符。当输入后的字符不为空字符,我们会和 focus 事件一样,重定位下一个需要聚焦的 input。如果输入的是多个字符(一般是是粘贴的缘故),就会把多出来的字符一个一个按顺序填入到后面的 input 中,然后才重定位光标。这样,我们就实现了一个个输入数字和粘贴短信验证码(一次性输入多个数字)的交互。

最后我们还要处理退格行为,需要给所有 input 绑定 keydown 事件。当按下的为退格键,且当前 input 的 value 为空时,清空上一个 input 里的数据,并聚焦到上一个 input 上。

对了,验证码输入错误后,需要清除所有 input 的数据,并把焦点移动到第一个 input 上。

总结

原理并不复,只是实现起来有点繁琐。

我这个方案没有进行浏览器兼容,请大家在经过充分的测试后再行使用。

如果可以的话,我还是推荐简单的一个输入框方案,而不是选择这种花里胡哨的交互。简单稳妥的实现维护简单,也不会有太多意想不到的状况。因为验证码输入这里如果在某些浏览器上无法正确操作,对转化率还是有很大影响的。

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 3,816评论 0 10
  •   JavaScript 与 HTML 之间的交互是通过事件实现的。   事件,就是文档或浏览器窗口中发生的一些特...
    霜天晓阅读 1,916评论 1 10
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 2,028评论 0 4
  • 不知不觉,岁寒输入法的更新历史已经可以列出这么一长串来了。从中可以看出,岁寒的发展过程也是一个不断试错的过程,其中...
    临岁之寒阅读 29,821评论 1 5
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 2,514评论 0 8