共计 6990 个字符,预计需要花费 18 分钟才能阅读完成。
最近在用
uni-app
开发时遇到一个相似微信领取的明码框需要,要求:用户输出明码后主动向后跳转一个输入框,并且取得焦点,直到输出结束。用户删除时,删除完以后输入框的内容,再按一个“退格 / 删除”键,则主动往前跳一个输入框,并将其内容删除。
成果如:
实现思路
- 有且只能有一个
input
输入框
如果采纳一个方框用一个input
输入框,在模拟器里没有什么问题,但在实在的手机中会呈现软件盘弹起不了问题。 - 肉眼看到的输入框 (方框) 是虚构的,光标也是虚构的
input
输入框的大小与方框大小统一,字体大小保持一致,字体色彩为通明,设置字体色彩为通明后输入框的光标也会随之隐没- 光标挪动到哪个方框,
input
输入框也要随之挪动 input
输入框限度最多只能输出 2 个字符,如果只有一个字符则要在该字符后面补一个空格
因为要实现以后输入框的值删除掉后再按一个“退格 / 删除”键以后输入框的前一个输入框的值也要删除掉性能- 用户在方框中输出一个字符后,
input
输入框立刻挪动到下一个方框,并且清空input
输入框 - 删除行为,在
input
输入框中应用input
事件来模仿“退格 / 删除”行为
代码实现
template
<template>
<view class="password-input-com" ref="passwordInputCom">
<input
ref="passwordInput"
v-model="inputValue"
:focus="inputFocus"
:style="{left: passwordInputLeft +'px'}"
type="text"
maxlength="2"
@input="onInput"
@blur="onBlur"
class="password-input">
<view class="virtual-input-list" ref="virtualInputList">
<view class="virtual-input-item"
v-for="(item, index) in virtualInputs"
:key="index"
:class="{security: mask,'input-focus': virtualInputItemIndex == index}"
@click="onVirtualInputClick(index)">
<view v-if="!mask" class="text-viewer">{{item.value}}</view>
<view v-show="item.value !=' '&& mask" class="security-mask"></view>
<view class="virtual-input-cursor"></view>
</view>
</view>
</view>
</template>
javascript
<script>
export default {
name: "PasswordInput",
props: {
value: {
type: String,
default: ''
},
length: { // 明码最大长度
type: Number,
default: 6
},
mask: {
type: Boolean,
default: false
}
},
data() {
// 获取运行平台
let getPlatform = () => {
let platform;
// #ifdef H5
platform = 'H5';
// #endif
// #ifdef MP-WEIXIN
platform = 'mp-weixin';
// #endif
// #ifdef MP-ALIPAY
platform = 'mp-alipay';
// #endif
return platform;
}
return {platform: getPlatform(),
virtualInputs: [],
// specialStr: '●', // 特殊字符
// splitStr: '★', // 宰割字符
inputValue: '',
inputFocus: false,
passwordInputLeft: 1,
virtualInputItemIndex: -1,
passwordInputComRect: {
width: 0,
height: 0,
left: 0,
right: 0
},
virtualInputItemRect: {
width: 0,
height: 0,
left: 0,
right: 0
}
};
},
watch: {
value: {
immediate: true,
handler(newVal){this.calcVirtualInputs(newVal);
}
}
},
methods: {
// 计算须要输入框的个数
calcVirtualInputs(newVal){let valueArr = ((newVal + '').length > 0 ? (newVal +'') : '●●●●●●●●●●●●●●●●●●●●●●●●').split('');
let length = this.length;
// console.log('valueArr', valueArr)
if(valueArr.length > length){valueArr.splice(length);
}else if(valueArr.length < length){
let lengthDiff = length - valueArr.length;
while(lengthDiff > 0){valueArr.push('●');
lengthDiff--;
}
}
let virtualInputs = valueArr.map((str, index) => {
return {
value: str == '●' ? ' ' : str,
focus: false,
index: index
};
});
this.virtualInputs = virtualInputs;
},
onInput(evt){// console.log(evt)
let val = evt.detail.value;
let virtualInputItemIndex = this.virtualInputItemIndex;
console.log('onInput', val);
if(val.length == 2){ // 以后虚构输入框输出值后立刻向后一个输入框挪动
this.virtualInputs[virtualInputItemIndex].value = val.charAt(1);
if((virtualInputItemIndex + 1) < this.length){
this.virtualInputItemIndex = virtualInputItemIndex + 1;
this.inputMoveTo(this.virtualInputItemIndex, () => {let nextVirtualInputVal = this.virtualInputs[this.virtualInputItemIndex].value;
console.log('nextVirtualInputVal', nextVirtualInputVal)
// 这里须要提早 60 毫秒再设置下一个虚构输入框的值,不然有效
let timer = setTimeout(() => {clearTimeout(timer);
this.inputValue = nextVirtualInputVal == '' ? nextVirtualInputVal : (' ' + nextVirtualInputVal);
console.log('this.inputValue', this.inputValue)
}, 60);
});
}
this.$nextTick(() => {this.detectInputComplete();
});
} else if(val.length == 1){console.log('length 等于 1', val)
if(val == ' '){ // 以后操作为删除虚构框中的值
this.virtualInputs[virtualInputItemIndex].value = ' ';
}else{ // 以后操作为正在输出
if((virtualInputItemIndex + 1) < this.length){
this.virtualInputItemIndex = virtualInputItemIndex + 1;
this.inputMoveTo(this.virtualInputItemIndex, () => {let nextVirtualInputVal = this.virtualInputs[this.virtualInputItemIndex].value;
let timer = setTimeout(() => {clearTimeout(timer);
this.inputValue = nextVirtualInputVal == '' ? nextVirtualInputVal : (' ' + nextVirtualInputVal);
console.log('this.inputValue2', this.inputValue)
}, 60);
});
}
this.$nextTick(() => {this.detectInputComplete();
});
}
} else if(val.length == 0){ // 往前一个输入框挪动,并删除其值
if(virtualInputItemIndex - 1 >= 0){
this.virtualInputItemIndex = virtualInputItemIndex - 1;
this.inputMoveTo(this.virtualInputItemIndex, () => {this.virtualInputs[this.virtualInputItemIndex].value = ' ';
// 这里须要提早 60 毫秒再设置下一个虚构输入框的值,不然有效
let timer = setTimeout(() => {clearTimeout(timer);
this.inputValue = ' ';
}, 60);
});
}
}
},
onBlur(){
this.inputFocus = false;
this.virtualInputItemIndex = -1;
},
detectInputComplete(){
let length = this.length;
let valStr = this.getValue();
console.log('detectInputComplete', valStr);
if(length == valStr.length){this.$emit('complete', valStr);
}
},
inputMoveTo(virtualInputIndex, cb){console.log('inputMoveTo', virtualInputIndex)
let passwordInputComRect = this.passwordInputComRect;
let obj = uni.createSelectorQuery().in(this).selectAll('.virtual-input-item');
// 获取元素宽高
obj.boundingClientRect((rectData) => {console.log(rectData)
let currentDomRect = rectData[virtualInputIndex];
console.log('currentDomRect', currentDomRect, virtualInputIndex, passwordInputComRect)
// + 1 是因为有 1px 的左边框
this.passwordInputLeft = currentDomRect.left - passwordInputComRect.left + 1;
typeof cb == 'function' ? cb() : 1;}).exec();},
onVirtualInputClick(index){console.log('onVirtualInputClick', index)
let $passwordInput = this.$refs.passwordInput;
this.inputMoveTo(index, () => {let virtualInputVal = this.virtualInputs[index].value;
this.inputFocus = true;
this.inputValue = virtualInputVal == '' ? virtualInputVal : (' ' + virtualInputVal);
this.virtualInputItemIndex = index;
if(this.platform == 'H5'){this.$refs.passwordInput.$el.focus();
}
});
},
getValue(){
let length = this.length;
let valStr = this.virtualInputs.reduce((res, item) => {let itemVal = item.value.replace(/ /g, '');
return res += itemVal;
}, '');
if(valStr.length > length){valStr = valStr.substr(0, length);
}
return valStr;
}
},
mounted() {this.$nextTick(() => {let obj = uni.createSelectorQuery().in(this).select('.password-input-com');
// 获取元素宽高
obj.boundingClientRect((data) => {if(!data){ // 支付宝小程序获取不到地位信息
let systemInfo = uni.getSystemInfoSync();
let wh = systemInfo.windowWidth;
let rpxCalcIncludeWidth = 750;
let pagePaddingLeft = 48;
data = {left: wh / 750 * 48}
}else{this.passwordInputComRect = data;}
console.log('组件宽高地位信息', data)
}).exec();});
}
}
</script>
css
<style lang="scss">
.password-input-com {position: relative;}
.password-input {
position: absolute;
top: 2rpx;
left: 2rpx;
width: 70rpx;
height: 100%;
line-height: 1.5;
/* color: rgba(255,255,255,0.8); */
color: transparent;
font-size: 48rpx;
text-align: center;
/* background-color: #f60; */
}
.virtual-input-list {
position: relative;
z-index: 2;
display: flex;
justify-content: space-between;
width: 100%;
height: 70rpx;
opacity: 0.7;
.virtual-input-item {
position: relative;
width: 70rpx;
height: 100%;
border: 2rpx solid #90949D;
transition: border-color .3s;
.text-viewer {
height: 100%;
line-height: 1.5;
text-align: center;
color: #202328;
font-size: 48rpx;
}
.security-mask {
position: absolute;
top: 50%;
left: 50%;
width: 24rpx;
height: 24rpx;
z-index: 4;
border-radius: 50%;
margin: -12rpx 0 0 -12rpx;
background-color: #202328;
}
.virtual-input-cursor {
display: none;
position: absolute;
top: 10%;
left: 50%;
height: 80%;
z-index: 6;
width: 3rpx;
background-color: #202328;
animation: 0.6s virtual-input-cursor infinite;
}
&.input-focus {
border-color: #387EE8;
.virtual-input-cursor {display: block;}
}
}
}
@keyframes virtual-input-cursor {
0% {opacity: 0;}
50% {opacity: 1;}
100% {opacity: 0;}
}
</style>
遗留问题
以上代码有个最大的问题就是:input
输入框的 type 只能为 text
,因为在实现的时候输入框的值后面会加上一个空格
如哪位大佬有更好的实现形式,请告知!万分感激!
正文完