使用VUE分分鐘寫一個驗證碼輸入元件

liusong發表於2017-12-14

效果

先來看波完成效果圖

效果圖

預覽地址

github地址

npm地址

需求

輸入4位或6位簡訊驗證碼,輸入完成後收起鍵盤

實現步驟

第一步

佈局排版

<div class="security-code-wrap">
    <label for="code">
      <ul class="security-code-container">
        <li class="field-wrap" v-for="(item, index) in number" :key="index">
          <i class="char-field">{{value[index] || placeholder}}</i>
        </li>
      </ul>
    </label>
    <input ref="input" class="input-code" @keyup="handleInput($event)" v-model="value"
           id="code" name="code" type="tel" :maxlength="number"
           autocorrect="off" autocomplete="off" autocapitalize="off">
</div>
複製程式碼
  • 使用li元素來模擬輸入框的顯示,沒有別的目的,就只是為了語義化,當然你也可以使用其他任意一個元素來模擬,比如div。
  • 使用label標籤的好處在於它可以跟input的click事件關聯上,一方面實現了語義化解決方案,另一方面也省去了我們通過js來喚起虛擬鍵盤。

隱藏輸入框

.input-code {
    position: absolute;
    left: -9999px;
    top: -9999px;
}
複製程式碼
  • 將真實的輸入框定位到螢幕可視區域以外的地方,虛擬鍵盤被喚起時,就不會將頁面往上頂了。所以你的驗證碼輸入元件一定要放在虛擬鍵盤遮擋不了的地方。

第二步

處理驗證碼輸入

handleSubmit () {
  this.$emit('input', this.value)
},
handleInput (e) {
  if (e.target.value.length >= this.length) {
    this.hideKeyboard()
  }
  this.handleSubmit()
}
複製程式碼
  • 輸入時,給輸入框賦一次值,是為了解決android端上輸入框失焦後重新聚焦,輸入游標會定在第一位的前面,經過賦值再聚焦,游標的位置就會顯示在最後一位後面。

第三步

完成輸入後關閉虛擬鍵盤

hideKeyboard() {
  // 輸入完成隱藏鍵盤
  document.activeElement.blur() // ios隱藏鍵盤
  this.$refs.input.blur() // android隱藏鍵盤
}
複製程式碼

元件完整程式碼

<template>
  <div class="security-code-wrap">
    <label :for="`code-${uuid}`">
      <ul :class="`${theme}-container security-code-container`">
        <li class="field-wrap" v-for="(item, index) in length" :key="index">
          <i class="char-field">{{value[index] || placeholder}}</i>
        </li>
      </ul>
    </label>
    <input ref="input" class="input-code" @keyup="handleInput($event)" v-model="value"
           :id="`code-${uuid}`" :name="`code-${uuid}`" type="tel" :maxlength="length"
           autocorrect="off" autocomplete="off" autocapitalize="off">
  </div>
</template>

<script>
  export default {
    name: 'SecurityCode',
    // component properties
    props: {
      length: {
        type: Number,
        default: 4
      },
      placeholder: {
        type: String,
        default: '-'
      },
      theme: {
        type: String,
        default: 'block'
      }
    },
    // variables
    data () {
      return {
        value: ''
      }
    },
    computed: {
      uuid () {
        return Math.random().toString(36).substring(3, 8)
      }
    },
    methods: {
      hideKeyboard () {
        // 輸入完成隱藏鍵盤
        document.activeElement.blur() // ios隱藏鍵盤
        this.$refs.input.blur() // android隱藏鍵盤
      },
      handleSubmit () {
        this.$emit('input', this.value)
      },
      handleInput (e) {
        if (e.target.value.length >= this.length) {
          this.hideKeyboard()
        }
        this.handleSubmit()
      }
    }
  }
</script>

<style scoped lang="less">
  .security-code-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .security-code-container {
    margin: 0;
    padding: 0;
    display: flex;
    .field-wrap {
      list-style: none;
      display: block;
      height: 40px;
      width: 40px;
      line-height: 40px;
      font-size: 16px;
      .char-field {
        font-style: normal;
      }
    }
  }

  .block-container {
    justify-content: center;
    .field-wrap {
      background-color: #fff;
      margin: 2px;
      color: #000;
    }
  }

  .line-container {
    position: relative;
    background-color: #fff;
    &:after {
      box-sizing: border-box;
      content: "";
      width: 200%;
      height: 200%;
      transform: scale(.5);
      position: absolute;
      border: 1px solid #d9d9d9;
      top: 0;
      left: 0;
      transform-origin: 0 0;
      border-radius: 4px;
    }
    .field-wrap {
      flex: 1;
      position: relative;
      &:not(:last-child):after {
        content: "";
        width: 1px;
        height: 50%;
        position: absolute;
        right: 0;
        top: 25%;
        background-color: #d9d9d9;
        transform: scaleX(.5);
      }
    }
  }

  .input-code {
    position: absolute;
    left: -9999px;
    top: -9999px;
  }

</style>

複製程式碼

元件使用程式碼

<security-code v-model="code"></security-code>
複製程式碼

結束語

怎麼樣,484 so easy

一開始的思路也是4個輸入框,監聽輸入完成跳到下一個輸入框,這樣的做法也能達到目的,不過需要更多的程式碼去維護這個規則,不夠優雅。

目前的做法已經是我能想到最優的解決方案,如果你有更好的實現思路,還望不吝賜教。

預覽地址

github地址

npm地址

相關文章