模仿密码方形输入框

1,008 阅读4分钟

前言

这是本人第一次掘金发文章,如有说的不对的地方欢迎指正!

1.需求

1.1 UI图

image.png

1.2介绍

需要做如上图所示的方形输入框,以前没做过,只是在很多地方见过,比如说银行卡密码输入,微信支付密码输入等场景

2.查找资料

2.1掘金

首先在掘金里面查找,但是找到的都是一些使用安卓原生写的,我这是微信H5,放弃!

2.2 百度

接下来直接百度,找到的都是类似的,瞧了一下,整理一下思路:都是放一个输入框,隐藏起来,放几个方形框显示输入的数字;思路有了直接开干

3.初步完成

<div style={{ boxSizing: 'border-box', padding: '16px 4px' }}>
        <div><span style={{ color: '#FF8A00' }}>*</span>请在指定位置上填写数字,无要求的位置可留空。</div>
        <div style={{ position: 'relative', marginTop: '8px' }}>
            <input ref={inputDiv} type="text"
                value={state.phoneNumber}
                onChange={(val) => {
                    changeValue(val);
                }}
                maxLength={11}
                style={{
                    border: '1px solid red',
                    opacity: '0'
                }}
            />
            <div onClick={getFocus} style={{
                position: 'absolute', top: '0', left: '0',
                display: 'flex', alignItems: 'center', background: '#fff', width: '100%', minHeight: '30px', boxSizing: 'border-box', paddingLeft: '8px'
            }}>
                {
                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
                        return (
                            <div key={Math.random() + index} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 2px', marginRight: (index === 2 || index === 6) ? '10px' : '', width: '26px', height: '30px', border: '1px solid #FF8A00', borderRadius: '2px', textAlign: 'center', lineHeight: '30px' }}>
                                {state.phoneNumber.substring(index, index + 1)}
                            </div>
                        )
                    })
                }
            </div>
        </div>
    </div>

3.1注意点

(1)由于我是使用react+ts,所以我使用useRef来获取输入框的焦点的,当点击方框那一块时,隐藏在下面的输入框就会聚焦

const inputDiv: React.RefObject = useRef(null);

const getFocus = () => { inputDiv.current!.focus();}

(2)获取输入框内容事件

const changeValue = (val: any) => {
    let newVal = val.target.value.replace(/[\D]/g, '');
    state.phoneNumber = newVal;
    state.update();
}

很简单的获取与赋值;其中state.phoneNumber = newVal;state.update();其实是setState((state) => state.phoneNumber = newVal);只不过我们技术老大封装了一下

(3)通过下标判断方形输入框的间隔,同时使用substring通过下标截取字段;

3.2上图

看起来已经初步实现了,按顺序输入没啥问题了;

image.png

3.3 缺陷

(1)点击后输入框聚焦了,也能输入,但是隐藏了,外面的方形框,没有光标啊,这就有点影响体验了 (2)按顺序输入是ok的,但是先填后面的,哦豁,就完蛋了!

4.完善所留问题

4.1添加光标

(1)方形框添加光标,直接百度光标css样式,很多,放在截取的字符后面就行,但是怎么显示呢? 我采用的是添加一个字段,点到哪个框就在进行下标赋值,上代码

            <div onClick={getFocus} style={{
                  position: 'absolute', top: '0', left: '0',
                  display: 'flex', alignItems: 'center', background: '#fff', width: '100%', minHeight: '30px', boxSizing: 'border-box', paddingLeft: '8px'
            }}>
                {
                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
                        return (
                            <div onClick={() => {
                                state.isShowCursor = index + 1;
                                state.update();
                            }} key={Math.random() + index} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 2px', marginRight: (index === 2 || index === 6) ? '10px' : '', width: '26px', height: '30px', border: '1px solid #FF8A00', borderRadius: '2px', textAlign: 'center', lineHeight: '30px' }}>
                                {state.phoneNumber.substring(index, index + 1)}
                                {state.isShowCursor == (index + 1) && <span className="blinker" style={{ display: 'inline-block', width: '1px', height: '22px', background: 'red' }}></span>}
                            </div>
                        )
                    })
                }
            </div>

(2)

image.png

我找的光标css带动画的一闪一闪的,图片上看不出来;至于为啥下标要加1再赋值呢,因为我初始值为0;

4.2输入顺序

(1)首先我要点到哪个框就在哪个框输入,这个可以,但是发现,输入的内容在第一个框内显示了,好吧,上代码;

const changeValue = (val: any) => {
    let newVal = val.target.value.replace(/[\D]/g, '');
    let newValArr = newVal.split('');

    // 拿到最新输入的值
    const lastVal = newValArr.find((val: string) => {
        console.log(newValArr, state.showPN.filter(v => v.trim()), val, state.showPN.filter(v => v.trim()).lastIndexOf(val), newValArr.lastIndexOf(val))
        if (!state.showPN.filter(v => v.trim()).includes(val)) {
            return val;
        }
        if (getItemIdx(state.showPN.filter(v => v.trim()), val) !== getItemIdx(newValArr, val)) {
            return val;
        }
    })

    //console.log('最新输入的值' + lastVal, '输入框内容:' + newVal, '和输入框内容相关联:' + state.phoneNumber, '当前输入的下标:' + state.isShowCursor,);
    if (state.isShowCursor <= 12) {
        if (state.phoneNumber.length < newVal.length) {
            state.showPN[state.isShowCursor - 1] = lastVal;
            state.isShowCursor++;
        } else {
            state.isShowCursor--;
            state.showPN[state.isShowCursor - 1] = '';
        }
    } else {
        state.isShowCursor = 0;
    }

    state.phoneNumber = newVal;
    state.update();
   
}

(2)有些JYM到这里可能就看不懂了,我又新添加了一个数组showPN, 我直接拿到输入框里面的内容直接插入到新数组里面,没填用星号替代;

image.png

当前方形框填完后,自动把光标移动到下一位;如果删除,自动把光标移动到前一位;全填了,则光标消失;

上面有一个getItemIdx()函数,是用来计算当前数字出现的次数,因为填号码肯定会出现重复的数字;

image.png

代码网上找的,命名不怎么规范,当时脑子已经懵了!

5.继续完善

5.1在全部填满的情况下,如果要修改其中的某一项,怎么办?

设置输入框插入光标,上代码


 // 设置输入框光标位置
const setInputCursor = (index: number) => {
    inputDiv.current!.setSelectionRange(index, index + 1);
}
if (state.isShowCursor == 12) {
    setInputCursor(index);
}

并且还要判断填满的时候才进行光标插入;要不然不对;

5.2 点击其他地方要失去焦点

这个好办,直接加blur事件,让state.isShowCursor初始化,自定义光标就消失了

 <input ref={inputDiv} type="text"
                value={state.phoneNumber}
                onChange={(val) => {
                    changeValue(val);
                }}
                onBlur={() => {
                    state.isShowCursor = 0;
                    state.update()
                }}
                maxLength={11}
                style={{
                    border: '1px solid red',
                    opacity: '0'
                }}
            />
            

但是如果聚焦后,再点另一个方形框,焦点消失了,再点才会出现!!所有这样不行,不能直接在input上加blur事件;那在哪加呢?在当前div的外面添加

image.png

ok,解决了;

6.整体代码

export default () => {

let state = useStateEx({
    phoneNumber: '',
    showPN: ['*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*'],
    isShowCursor: 0
})

const inputDiv: React.RefObject<HTMLInputElement> = useRef(null);

useEffect(() => { }, [])

const getFocus = () => {
    inputDiv.current!.focus();
}

const changeValue = (val: any) => {
    let newVal = val.target.value.replace(/[\D]/g, '');
    let newValArr = newVal.split('');

    // 拿到最新输入的值
    const lastVal = newValArr.find((val: string) => {
        console.log(newValArr, state.showPN.filter(v => v.trim()), val, state.showPN.filter(v => v.trim()).lastIndexOf(val), newValArr.lastIndexOf(val))
        if (!state.showPN.filter(v => v.trim()).includes(val)) {
            return val;
        }
        if (getItemIdx(state.showPN.filter(v => v.trim()), val) !== getItemIdx(newValArr, val)) {
            console.log(getItemIdx(state.showPN.filter(v => v.trim()), val), getItemIdx(newValArr, val), '2222222222222222')
            return val;
        }
    })


    console.log(lastVal, state.isShowCursor);

    //console.log('最新输入的值' + lastVal, '输入框内容:' + newVal, '和输入框内容相关联:' + state.phoneNumber, '当前输入的下标:' + state.isShowCursor,);
    if (state.isShowCursor <= 12) {
        if (state.phoneNumber.length < newVal.length) {
            state.showPN[state.isShowCursor - 1] = lastVal;
            state.isShowCursor++;
        } else {
            console.log('1111111111111111111111111111111111111111');
            state.isShowCursor--;
            state.showPN[state.isShowCursor - 1] = '';
        }
    } else {
        state.isShowCursor = 0;
    }

    state.phoneNumber = newVal;
    state.update();
    //console.log('实际显示的内容:' + state.showPN, '当前输入的下标:' + state.isShowCursor, '和输入框内容相关联:' + state.phoneNumber)
}

// 设置输入框光标位置
const setInputCursor = (index: number) => {
    inputDiv.current!.setSelectionRange(index, index + 1);
}


return <>
    <div style={{ boxSizing: 'border-box', padding: '16px 4px' }}>
        <div><span style={{ color: '#FF8A00' }}>*</span>请在指定位置上填写数字,无要求的位置可留空。</div>
        <div style={{ position: 'relative', marginTop: '8px' }}>
            <input ref={inputDiv} type="text"
                value={state.phoneNumber}
                onChange={(val) => {
                    changeValue(val);
                }}
                onBlur={() => {
                    state.isShowCursor = 0;
                    state.update()
                }}
                maxLength={11}
                style={{
                    border: '1px solid red',
                    opacity: '0'
                }}
            />
            <div onClick={getFocus} style={{
                position: 'absolute', top: '0', left: '0',
                display: 'flex', alignItems: 'center', background: '#fff', width: '100%', minHeight: '30px', boxSizing: 'border-box', paddingLeft: '8px'
            }}>
                {
                    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((item, index) => {
                        return (
                            <div onClick={() => {
                                state.isShowCursor = index + 1;
                                state.update();
                                if (state.isShowCursor == 12) {
                                    setInputCursor(index);
                                }
                            }} key={Math.random() + index} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 2px', marginRight: (index === 2 || index === 6) ? '10px' : '', width: '26px', height: '30px', border: '1px solid #FF8A00', borderRadius: '2px', textAlign: 'center', lineHeight: '30px' }}>
                                {state.showPN[index].replace(/[\D]/g, '')}
                                {state.isShowCursor == (index + 1) && <span className="blinker" style={{ display: 'inline-block', width: '1px', height: '22px', background: 'red' }}></span>}
                            </div>
                        )
                    })
                }
            </div>
        </div>
    </div>
</>

};

7.结束了,可能还有一些地方不完善,欢迎各路大佬指正!最后

image.png