Vue實(shí)現(xiàn)驗(yàn)證碼輸入交互

最近做一個(gè)H5的頁(yè)面焕刮,里面有個(gè)輸入驗(yàn)證碼交互,就是移動(dòng)端比較常見(jiàn)的那種驗(yàn)證碼輸入交互墙杯。就是那種配并,對(duì),就是那種霍转,一個(gè)數(shù)字一個(gè)下劃線荐绝,移動(dòng)端非常常見(jiàn)的那種驗(yàn)證碼交互。實(shí)現(xiàn)過(guò)程中主要參考了美團(tuán)外賣(mài)安卓端的具體交互避消。

文章首發(fā)于我的 個(gè)人博客低滩。

應(yīng)用到項(xiàng)目中的效果如下召夹。

一般操作.gif
粘貼效果

方案選擇

方案1:調(diào)整文字的間距

設(shè)置 input 的 letter-spacing 屬性,我們就可以讓驗(yàn)證碼之間有足夠大的空隙恕沫,然后再把底線改為有間隔的多個(gè)線段貌似就可以了监憎。

然而,這里會(huì)有一個(gè)問(wèn)題婶溯。就是光標(biāo)總是會(huì)在數(shù)字的左邊鲸阔,而我們希望的是輸入后的數(shù)字的中心位于原來(lái)光標(biāo)的位置。最終我放棄了這個(gè)方案迄委。

顯然褐筛,這個(gè)方案并不合適。

方案2:使用多個(gè) input

這就是我使用的方式叙身,也是接下來(lái)我要詳細(xì)講解的方案渔扎。主要原理是:使用多個(gè) input 元素,每個(gè) input 只能輸入一個(gè)數(shù)字信轿。當(dāng)通過(guò) input 事件監(jiān)測(cè)到字符輸入時(shí)晃痴,自動(dòng)將焦點(diǎn)對(duì)焦到下一個(gè) input 元素。

當(dāng)然我們還要實(shí)現(xiàn)點(diǎn)擊任何一個(gè)輸入框時(shí)财忽,將焦點(diǎn)移動(dòng)到第一個(gè)value為空的input上倘核。另外,點(diǎn)擊退格鍵時(shí)即彪,也要進(jìn)行焦點(diǎn)的改變紧唱。

測(cè)試后后發(fā)現(xiàn),焦點(diǎn)的移動(dòng)祖凫,不會(huì)導(dǎo)致移動(dòng)端鍵盤(pán)的收起琼蚯。最終我就決定使用這個(gè)方案了酬凳。

代碼實(shí)現(xiàn)

在線示例: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];   // 最后一個(gè)碼惠况,只允許輸入一個(gè)字符。
      } 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();
      }
    },
    // 重置光標(biāo)位置宁仔。
    resetCaret() {
      this.$refs.input[this.ctSize-1].focus();
    },
    onFocus() {
      // 監(jiān)聽(tīng) focus 事件稠屠,將光標(biāo)重定位到“第一個(gè)空白符的位置”。
      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 === '') {
        // 刪除上一個(gè)input里的值翎苫,并對(duì)其focus权埠。
        if (index > 0) {
          this.ct[index - 1] = '';
          this.$refs.input[index - 1].focus();
        }
      }
    },
    sendCaptcha() {
      console.log();
      this.msg = `發(fā)送驗(yàn)證碼到服務(wù)器:${this.ct.join('')}`;
      // 此時(shí)無(wú)法操作 input。煎谍。
      this.loading = true;
      setTimeout(() => {
        this.msg = ('驗(yàn)證碼錯(cuò)誤')
        this.loading = false;
        this.$nextTick(() => {
          this.reset();
        })
      }, 3000)
    },

    reset() {
      // 重置攘蔽。一般是驗(yàn)證碼錯(cuò)誤時(shí)觸發(fā)。
      this.ct = this.ct.map(item => '');
      this.resetCaret();
    }
  }
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')

原理

創(chuàng)建多個(gè) input 元素呐粘,對(duì)這些 input 都綁定 focus 事件满俗。一旦觸發(fā)該事件转捕,我們會(huì)把焦點(diǎn)移動(dòng)到從左往右第一個(gè) value 為空字符的 input 上。所以在初始狀態(tài)時(shí)唆垃,點(diǎn)擊最右邊的 input五芝,光標(biāo)還是會(huì)跑到最左邊的 input。

然后我們給這些 input 綁定 input 事件辕万,監(jiān)聽(tīng)輸入字符枢步。當(dāng)輸入后的字符不為空字符,我們會(huì)和 focus 事件一樣渐尿,重定位下一個(gè)需要聚焦的 input醉途。如果輸入的是多個(gè)字符(一般是是粘貼的緣故),就會(huì)把多出來(lái)的字符一個(gè)一個(gè)按順序填入到后面的 input 中砖茸,然后才重定位光標(biāo)结蟋。這樣,我們就實(shí)現(xiàn)了一個(gè)個(gè)輸入數(shù)字和粘貼短信驗(yàn)證碼(一次性輸入多個(gè)數(shù)字)的交互渔彰。

最后我們還要處理退格行為嵌屎,需要給所有 input 綁定 keydown 事件。當(dāng)按下的為退格鍵恍涂,且當(dāng)前 input 的 value 為空時(shí)宝惰,清空上一個(gè) input 里的數(shù)據(jù),并聚焦到上一個(gè) input 上再沧。

對(duì)了尼夺,驗(yàn)證碼輸入錯(cuò)誤后,需要清除所有 input 的數(shù)據(jù)炒瘸,并把焦點(diǎn)移動(dòng)到第一個(gè) input 上淤堵。

總結(jié)

原理并不復(fù),只是實(shí)現(xiàn)起來(lái)有點(diǎn)繁瑣顷扩。

我這個(gè)方案沒(méi)有進(jìn)行瀏覽器兼容拐邪,請(qǐng)大家在經(jīng)過(guò)充分的測(cè)試后再行使用。

如果可以的話隘截,我還是推薦簡(jiǎn)單的一個(gè)輸入框方案扎阶,而不是選擇這種花里胡哨的交互。簡(jiǎn)單穩(wěn)妥的實(shí)現(xiàn)維護(hù)簡(jiǎn)單婶芭,也不會(huì)有太多意想不到的狀況东臀。因?yàn)轵?yàn)證碼輸入這里如果在某些瀏覽器上無(wú)法正確操作,對(duì)轉(zhuǎn)化率還是有很大影響的犀农。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惰赋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呵哨,更是在濱河造成了極大的恐慌赁濒,老刑警劉巖贵扰,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異流部,居然都是意外死亡戚绕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)枝冀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舞丛,“玉大人,你說(shuō)我怎么就攤上這事果漾∏蚯校” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵绒障,是天一觀的道長(zhǎng)吨凑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)户辱,這世上最難降的妖魔是什么鸵钝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮庐镐,結(jié)果婚禮上恩商,老公的妹妹穿的比我還像新娘。我一直安慰自己必逆,他們只是感情好怠堪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著名眉,像睡著了一般粟矿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上损拢,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天陌粹,我揣著相機(jī)與錄音,去河邊找鬼探橱。 笑死申屹,一個(gè)胖子當(dāng)著我的面吹牛绘证,可吹牛的內(nèi)容都是我干的隧膏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嚷那,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼胞枕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起魏宽,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腐泻,失蹤者是張志新(化名)和其女友劉穎决乎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體派桩,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡构诚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铆惑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片范嘱。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖员魏,靈堂內(nèi)的尸體忽然破棺而出丑蛤,到底是詐尸還是另有隱情,我是刑警寧澤撕阎,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布受裹,位于F島的核電站,受9級(jí)特大地震影響虏束,放射性物質(zhì)發(fā)生泄漏棉饶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一镇匀、第九天 我趴在偏房一處隱蔽的房頂上張望砰盐。 院中可真熱鬧,春花似錦坑律、人聲如沸岩梳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冀值。三九已至,卻和暖如春宫屠,著一層夾襖步出監(jiān)牢的瞬間列疗,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工浪蹂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抵栈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓坤次,卻偏偏與公主長(zhǎng)得像古劲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缰猴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一产艾、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 5,744評(píng)論 0 10
  • ??JavaScript 與 HTML 之間的交互是通過(guò)事件實(shí)現(xiàn)的闷堡。 ??事件隘膘,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,490評(píng)論 1 11
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,381評(píng)論 0 5
  • 不知不覺(jué),歲寒輸入法的更新歷史已經(jīng)可以列出這么一長(zhǎng)串來(lái)了杠览。從中可以看出弯菊,歲寒的發(fā)展過(guò)程也是一個(gè)不斷試錯(cuò)的過(guò)程,其中...
    臨歲之寒閱讀 33,968評(píng)論 1 6
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說(shuō)明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí)踱阿,會(huì)觸發(fā)此異常误续。 O...
    我想起個(gè)好名字閱讀 5,311評(píng)論 0 9