vue实现一个6个输入框的验证码输入组件

2020-06-30 02:57:22vue.js, 验证码framework

image.png

要实现的功能:

完全和单输入框一样的操作,甚至可以插入覆盖:
1,限制输入数字
2,正常输入
3,backspace删除
4,paste任意位置粘贴输入
5,光标选中一个数字,滚轮可以微调数字大小,限制0-9
6,123|456 自动覆盖光标后输入的字符,此时光标在3后,继续输入111,会得到123111,而不用手动删除456
7,封装成vue单文件组件,方便任意调用。

模板代码

<template>
    <div>
        <div @keydown="keydown" @keyup="keyup" @paste="paste" @mousewheel="mousewheel"
                @input="inputEvent">
            <input max="9" min="0" maxlength="1" data-index="0" v-model.trim.number="input[0]" type="number"
                    ref="firstinput"/>
            <input max="9" min="0" maxlength="1" data-index="1" v-model.trim.number="input[1]" type="number"/>
            <input max="9" min="0" maxlength="1" data-index="2" v-model.trim.number="input[2]" type="number"/>
            <input 
            <input max="9" min="0" maxlength="1" data-index="4" v-model.trim.number="input[4]" type="number"/>
            <input max="9" min="0" maxlength="1" data-index="5" v-model.trim.number="input[5]" type="number"/>
        </div>
    </div>
</template>

实现了键盘的keydown/keyup/paste/input和鼠标滚轮mousewheel事件
使用了6个输入框的方案来实现。

样式部分:使用了scss模式

<style scoped lang="scss">
    .input-box {
        .input-content {
            width: 512px;
            height: 60px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            
            input {
                color: inherit;
                font-family: inherit;
                border: 0;
                outline: 0;
                border-bottom: 1px solid #919191;
                height: 60px;
                width: 60px;
                font-size: 44px;
                text-align: center;
            }
        }
        
        input::-webkit-outer-spin-button,
        input::-webkit-inner-spin-button {
            appearance: none;
            margin: 0;
        }
    }
</style>

具体实现逻辑:主要实现以上几个键盘事件操作。

<script>
    export default {
        data() {
            return {
                pasteResult: [],
            };
        },
        props: ['code'],
        computed: {
            input() {
                if (this.code && Array.isArray(this.code) && this.code.length === 6) {
                    return this.code;
                } else if (/^\d{6}$/.test(this.code.toString())) {
                    return this.code.toString().split('');
                } else if (this.pasteResult.length === 6) {
                    return this.pasteResult;
                } else {
                    return new Array(6);
                }
            }
        },
        methods: {
            // 解决一个输入框输入多个字符
            inputEvent(e) {
                var index = e.target.dataset.index * 1;
                var el = e.target;
                el.value = el.value.replace(/Digit|Numpad/i, '').replace(/1/g, '').slice(0, 1);
                this.$set(this.input, index, el.value)
            },
            keydown(e) {
                var index = e.target.dataset.index * 1;
                var el = e.target;
                if (e.key === 'Backspace') {
                    if (this.input[index].length > 0) {
                        this.$set(this.input, index, '')
                    } else {
                        if (el.previousElementSibling) {
                            el.previousElementSibling.focus()
                            this.$set(this.input, index - 1, '')
                        }
                    }
                } else if (e.key === 'Delete') {
                    if (this.input[index].length > 0) {
                        this.$set(this.input, index, '')
                    } else {
                        if (el.nextElementSibling) {
                            this.$set(this.input, index = 1, '')
                        }
                    }
                    if (el.nextElementSibling) {
                        el.nextElementSibling.focus()
                    }
                } else if (e.key === 'Home') {
                    el.parentElement.children[0] && el.parentElement.children[0].focus()
                } else if (e.key === 'End') {
                    el.parentElement.children[this.input.length - 1] && el.parentElement.children[this.input.length - 1].focus()
                } else if (e.key === 'ArrowLeft') {
                    if (el.previousElementSibling) {
                        el.previousElementSibling.focus()
                    }
                } else if (e.key === 'ArrowRight') {
                    if (el.nextElementSibling) {
                        el.nextElementSibling.focus()
                    }
                } else if (e.key === 'ArrowUp') {
                    if (this.input[index] * 1 < 9) {
                        this.$set(this.input, index, (this.input[index] * 1 + 1).toString());
                    }
                } else if (e.key === 'ArrowDown') {
                    if (this.input[index] * 1 > 0) {
                        this.$set(this.input, index, (this.input[index] * 1 - 1).toString());
                    }
                }
            },
            keyup(e) {
                var index = e.target.dataset.index * 1;
                var el = e.target;
                // 解决输入e的问题
                el.value = el.value.replace(/Digit|Numpad/i, '').replace(/1/g, '').slice(0, 1);
                if (/Digit|Numpad/i.test(e.code)) {
                    // 必须在这里符直,否则输入框会是空值
                    this.$set(this.input, index, e.code.replace(/Digit|Numpad/i, ''));
                    el.nextElementSibling && el.nextElementSibling.focus();
                    if (index === 5) {
                        if (this.input.join('').length === 6) {
                            document.activeElement.blur();
                            this.$emit('complete', this.input);
                        }
                    }
                } else {
                    if (this.input[index] === '') {
                        this.$set(this.input, index, '');
                    }
                }
            },
            mousewheel(e) {
                var index = e.target.dataset.index;
                if (e.wheelDelta > 0) {
                    if (this.input[index] * 1 < 9) {
                        this.$set(this.input, index, (this.input[index] * 1 + 1).toString());
                    }
                } else if (e.wheelDelta < 0) {
                    if (this.input[index] * 1 > 0) {
                        this.$set(this.input, index, (this.input[index] * 1 - 1).toString());
                    }
                } else if (e.key === 'Enter') {
                    if (this.input.join('').length === 6) {
                        document.activeElement.blur();
                        this.$emit('complete', this.input);
                    }
                }
            },
            paste(e) {
                // 当进行粘贴时
                e.clipboardData.items[0].getAsString(str => {
                    if (str.toString().length === 6) {
                        this.pasteResult = str.split('');
                        document.activeElement.blur();
                        this.$emit('complete', this.input);
                        this.pasteResult = [];
                    } else {
                        // 如果粘贴内容不合规,清除所有内容
                        this.input[0] = new Array(6)
                    }
                })
            }
        },
        mounted() {
            // 等待dom渲染完成,在执行focus,否则无法获取到焦点
            this.$nextTick(() => {
                this.$refs.firstinput.focus()
            })
        },
    }
</script>

如果你发现了bug,或者有优化空间,欢迎你的指正和建议。我会随时更新到原代码当中,分享给大家。

热门评论:

  • nanzk8fL nanzk8fL 2020-6-29 8:58 回复:

    哈哈,好漂亮,功能也好全。另外我发现一个不知道能不能优化的地方,就是我按键盘上的字母e键,能输入eeeee字,而且貌似清理不了

    • jsoncode jsoncode 2020-6-29 9:01 回复:nanzk8fL
      这个e虽然也是数字组成的一部分(1e2=10的2次方),但是看你的操作,应该也算是bug了,我看一下试试修复一下。非常感谢反馈。

    • nanzk8fL nanzk8fL 2020-6-29 9:05 回复:nanzk8fL

      @jsoncode 找到了别人说的一个方法,input标签加个onKeyUp="this.value=this.value.replace(/1/g,试了一下可以。虽然输了e就闪掉了,感觉还好。谢谢大佬的分享的这个验证码组件。非常感谢

  1. .d
  • jsoncode jsoncode 2020-6-29 9:35 回复:nanzk8fL
    @nanzk8fL[nanzk8fl] 你提到的问题已经更新到文章里了,非常感谢反馈。

  • coderOG coderOG 2020-6-29 12:18 回复:

    前几个为空从第二或第三开始输入时,最后一位可以无限输,失焦时才正确,这块可以优化下。。

    • coderOG coderOG 2020-6-29 2:12 回复:coderOG

      @jsoncode 现在是最后一个可以输入很多字符

    • jsoncode jsoncode 2020-6-30 2:59 回复:coderOG
      @coderOG[coderog] 已修复


  • hahah hahah 2020-6-29 3:13 回复:

    我复制了,代码又问题哇

    • jsoncode jsoncode 2020-6-29 4:56 回复:hahah
      有什么问题你说赛?欢迎指正和优化。

  • Letme Letme 2020-6-30 10:58 回复:

    输入控件中间空一位或者2位,最后一位就可以输入大于1的数多个,只有在失去焦点的时候才正常

    • jsoncode jsoncode 2020-6-30 2:58 回复:Letme
      我最后重现出来的bug是,可以输入多个000,其他数字不行。所以楼里反馈的没用人说太具体,测试了2天没重现这个bug.现在已经修复了

    • Letme Letme 2020-6-30 7:55 回复:Letme

      @jsoncode


  • Cho Rong Cho Rong 2020-7-10 5:52 回复:

    bug好多。手机端输入不了,PC端输1不显示要再输一个数字才行,而且可以选中非第一位的输入框。。

    • jsoncode jsoncode 2020-7-11 9:34 回复:Cho Rong
      1,不要抱怨,有问题和意见随便提,我都会解决。
      2,已确认手机确实不能输入,我修复一下。
      3,pc输入1正常输入,没发现你提的bug
      4,可以复制有什么问题?随便复制好了,我又不管这个。

    • 小小布丁 小小布丁 2020-8-17 4:31 回复:Cho Rong

      @jsoncode 现在的代码有最新的吗


  • beelzahan beelzahan 2021-8-3 3:58 回复:

    粘贴功能只能粘贴第一位吗

    • jsoncode jsoncode 2021-8-31 5:55 回复:beelzahan
      4,paste任意位置粘贴输入,如果你发现bug,随时再反馈哦。

  • 其实不奇怪 其实不奇怪 2021-8-31 5:53 回复:

    你好 我刚从原生转h5,需要去掉数字的限制,我们有字母和数字,我单纯改了输入框,字符不能连续输入

    • jsoncode jsoncode 2021-8-31 5:56 回复:其实不奇怪
      /^\d{6}$/ 是用来判断数字的,所以你可以把这一块逻辑修改一下。

    • jsoncode jsoncode 2021-8-31 5:56 回复:其实不奇怪
      理论上,你可以修改成任意长度的任意字符的输入

    • 其实不奇怪 其实不奇怪 2021-9-1 5:14 回复:其实不奇怪

      感谢如此迅速的回复,我已经改好了/Digit|Numpad|Key/。现在有一个问题,数字键盘输入*,/就会变成好长好长的英文单词,正则都不知道往哪放,哈哈 暂时不处理了


  • 柒kZBc6 柒kZBc6 2021-9-6 10:50 回复:

    PC端输入太快会有空的


  • 其实不奇怪 其实不奇怪 2021-9-9 2:10 回复:

    大佬,我又来了,IE浏览器发现一个bug,特来提交,哈哈
    https://developer.mozilla.org...

    • jsoncode jsoncode 2021-11-1 11:06 回复:其实不奇怪
      我现在电脑都没有ie浏览器了,ie还是老老实实用一个输入框吧 :D