今天在项目中需要使用一个验证码输入框,上图:
由于压缩了,GIF不太清晰,补个原图:
刚开始想的是使用四个input,输入时聚焦到下一个,删除则聚焦到上一个,仔细一想太麻烦,放弃了。之后想就使用一个input,把letter-spacing调大,再去控制四个div的CSS,不优雅,而且下次复用直接完蛋,也放弃了。这个输入框最大的难点就在于光标位置切换,一直转进input聚焦的死胡同里了,后来灵光一闪: 给一个active类,利用伪元素结合动画模拟光标,动态切换四个div的active类即可。
首先,我们现在需要的是4个div,以后也可能是6个、5个,因此不能直接在组件内部写4个div,而应该通过props传递需要的个数size,v-for循环生成。使用v-model接收最后输入的字符串:
<ih-confirm-code v-model="confirmCode" size="4"></ih-confirm-code>
知道了需要的div数量,把几个框弄出来:传递过来的size可能是数字类型,也可能是字符串类型,先在计算属性maxLength中判断数据类型,如果是数字类型直接返回,否则转换为数字类型后再返回。接下来new一个长度为maxLength的空数组,最后通过v-for循环生成数量为size的div。CSS部分随便写,下次不想改代码的话,背景、边框啥的都用props动态绑定吧!
<template>
<div class="box">
<div v-for="(item,index) in loopDiv" :key="index" class="item"></div>
</div>
</template>
<script>
export default {
props:{
size:{
type:[String,Number],
default:4
}
},
data(){
return {
}
},
computed:{
maxLength(){
return typeof this.size == "number" ? this.size : Number(this.size);
},
loopDiv(){
return new Array(this.maxLength);
}
}
}
</script>
<style scoped>
.box{
display: flex;
justify-content: space-between;
cursor: text;
}
.item{
display:flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
border: 1px solid hsla(0,0%,100%,.08);
border-radius: 10px;
background: rgba(0,0,0,.2);
color: #fff;
font-size: 30px;
position: relative;
}
</style>
单有四个框肯定还是差远了,不能输入,得弄个input:input值我们需要,绑定code,壳壳就不要了:transform:scale(0),看都看不见了就别占地了:position:absolute给它挪个窝。
<template>
<div class="confirm-warpper">
<input ref="input" v-model="code" type="number" />
<div class="box">
<div v-for="(item,index) in loopDiv" :key="index" class="item"></div>
</div>
</div>
</template>
<script>
export default {
props:{
size:{
type:[String,Number],
default:4
}
},
data(){
return {
code:""
}
},
computed:{
maxLength(){
return typeof this.size == "number" ? this.size : Number(this.size);
},
loopDiv(){
return new Array(this.maxLength);
}
}
}
</script>
<style scope>
input{
position:absolute;
transform:scale(0);
}
</style>
壳没了但是还是要能聚焦input,不然一旦错过就不在,两种情况:1.刚进来直接聚焦(mounted钩子);2.点击框聚焦input(.box点击事件)。再一个就是怎样模拟聚焦的问题,变量current = this.code.length(记录聚焦div的下标,在监听器code中随输入改变),没有输入的时候聚焦下标为0的div(index = 0,current = this.code.length = 0),输入一个字符聚焦下标为1的div(index = 1,current = 1)....输入完时,光标消失(index = 3, current = this.code.length = 4)。因此,当current == index时在该div上动态绑定active类,active有个伪元素before,模拟光标聚焦。完整代码:
<template>
<div class="confirm-warpper">
<input ref="input" v-model="code" @blur="lose" type="number" />
<div @click="focus" class="box">
<div v-for="(item,index) in loopDiv" :key="index" class="item" :clas="{active:current == index}">{{code[index]}}<div>
</div>
</div>
</template>
<script>
export default {
props:{
size:{
type:[String,Number],
default:4
}
},
data(){
return {
code:"",
current:0
}
},
watch:{
code(){
this.code = this.code.toString().slice(0,maxLength);
this.current = this.code.length;
this.$emit("input",this.code);
}
},
computed:{
maxLength(){
return typeof this.size == "number" ? this.size : Number(this.size);
},
loopDiv(){
return new Array(this.maxLength);
}
},
methods:{
focus(){
this.$refs.input.focus();
var len = this.code.length;
// 如果已经输满,点击则聚焦在最后一个字符
if(len == this.maxLength){
this.current = this.code.length - 1;
}else{
this.current = this.code.length;
}
},
//input失焦触发,等于-1防止出现size符合偶然情况,失焦后又聚焦某一div
lose(){
this.current = -1;
}
},
mounted(){
this.focus();
}
}
</script>
<style scoped>
@keyframes cursor{
0%{
opacity: 0;
}
50%{
opacity: 1;
}
100%{
opacity: 0;
}
}
input{
position: absolute;
transform: scale(0);
}
.box{
display: flex;
justify-content: space-between;
cursor: text;
}
.item{
display:flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
border: 1px solid hsla(0,0%,100%,.08);
border-radius: 10px;
background: rgba(0,0,0,.2);
color: #fff;
font-size: 30px;
position: relative;
}
.item.active::before{
content:"";
position: absolute;
top: 50%;
left: 70%;
transform: translate(-50%,-50%);
height: 40px;
width: 2px;
background: #fff;
animation: cursor 1s infinite;
}
</style>
补充:
this.code = this.code.toString().slice(0,maxLength);
code监听器写这行代码是由于input的type=“number”,maxlength不起作用,没法限制限制他的最大长度,只能先转string类型,再截取0~maxLength这一段字符串并重新赋值,实现this.code最大长度 == size的效果,避免超出。用type=“text”的话,虽然可以直接限制长度,也可以用this.code = parseInt(this.code)过滤掉非数字字符,但小数点逃不掉,而且没事出两拼音挺不爽的。
文中可能会有一些错误,写的时候也是作为白天的复盘,如有错误请指出!