前言
这是本人第一次掘金发文章,如有说的不对的地方欢迎指正!
1.需求
1.1 UI图
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上图
看起来已经初步实现了,按顺序输入没啥问题了;
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)
我找的光标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, 我直接拿到输入框里面的内容直接插入到新数组里面,没填用星号替代;
当前方形框填完后,自动把光标移动到下一位;如果删除,自动把光标移动到前一位;全填了,则光标消失;
上面有一个getItemIdx()函数,是用来计算当前数字出现的次数,因为填号码肯定会出现重复的数字;
代码网上找的,命名不怎么规范,当时脑子已经懵了!
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的外面添加
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>
</>
};