最近在用uni-app开发时遇到一个相似微信领取的明码框需要,要求:用户输出明码后主动向后跳转一个输入框,并且取得焦点,直到输出结束。用户删除时,删除完以后输入框的内容,再按一个“退格/删除”键,则主动往前跳一个输入框,并将其内容删除。

成果如:

实现思路

  1. 有且只能有一个input输入框
    如果采纳一个方框用一个input输入框,在模拟器里没有什么问题,但在实在的手机中会呈现软件盘弹起不了问题。
  2. 肉眼看到的输入框(方框)是虚构的,光标也是虚构的
  3. input输入框的大小与方框大小统一,字体大小保持一致,字体色彩为通明,设置字体色彩为通明后输入框的光标也会随之隐没
  4. 光标挪动到哪个方框,input输入框也要随之挪动
  5. input输入框限度最多只能输出2个字符,如果只有一个字符则要在该字符后面补一个空格
    因为要实现以后输入框的值删除掉后再按一个“退格/删除”键以后输入框的前一个输入框的值也要删除掉性能
  6. 用户在方框中输出一个字符后,input输入框立刻挪动到下一个方框,并且清空input输入框
  7. 删除行为,在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,因为在实现的时候输入框的值后面会加上一个空格
如哪位大佬有更好的实现形式,请告知!万分感激!