2018年07月11日

基于vue的限制输入框可输入字节数的解决方案

需求:input输入框输入上限4个字节、达到上限则不能继续输入,其中1个英文表示1个字节、1个中文表示2个字节。

看到这个需求,第一个想到的就是input元素的maxlength属性。

MDN对input的maxlength属性的说明是:

如果 type 的值是 text, email, search, password, tel, 或 url,那么这个属性指明了用户最多可以输入的字符个数(按照Unicode编码方式计数);对于其他类型的输入框,该属性被忽略。它可以大于 size 属性的值。如果不指定这个属性,那么用户可以输入任意多的字符。如果指定为一个负值,那么元素表现出默认行为,即用户可以输入任意多的字符。本属性的约束规则,仅在元素的 value 属性发生变化时才会执行。译者注:ie10+

从中我们可以得知两个有用信息:

maxlength可以用来限制输入长度;

maxlength是按字符数进行限制的,不能区分中英文。

根据这两个信息,我们得到了实现此需求的基本方案:

在用户输入之后计算输入内容的字节数和剩余可输入字节数,并动态地改变input元素的maxlength属性值。

计算输入内容的字节数

将输入内容的每一个中文字符替换成两个英文字符,计算其字符长度:

// 返回字符串str的长度,其中中文占2个长度单位,英文等字符占1个长度单位

let length = (str) => {

var r = /[^\x00-\xff]/g

return str.replace(r, 'mm').length

}

动态地改变maxlength值

思考解决方法过程中,分别实现过 通过@input事件触发方法来修改maxlength、把处理方法放到mixin里、封装一个处理方法的指令 3种方式,考虑可移植性及修改作用域字段的可行性,最终采用指令方式。

下面对主要逻辑进行说明:

maxlength:input元素maxlength属性值

limit:限制字节数

中文多消耗的字节数中文字符个数maxlength = limit - 中文多消耗的字节数 = limit - 中文字符个数

当连续输入的内容的字节数超过limit时,需要对内容进行截断:

截断长度 = 一个一个输入的非中文字符串长度 + 允许输入的中文字符串长度 = 非中文字符长度 + Math.floor((limit - 非中文字符所占字节数) / 2)

截断后内容 = 截断前内容.substring(0, 截断长度)

当在两个非中文之间(非末尾)输入超过限制的中文时,截取长度会多1个字节,需要再次校准输入内容:

校准后内容 = 校准前内容.substring(0, 截断长度 - 1)

vue自定义指令的钩子update是在VNode更新时调用,所以我们的处理方法在update钩子中进行;

vue自定义指令钩子函数会被传入参数el、binding、vnode、oldVnode。我们可以通过el拿到指定绑定的DOM;通过binding拿到指令的绑定值和传入的参数等等;通过vnode可以拿到vue编译生成的虚拟节点,其中vnode.context是虚拟节点上下文、也就是this,通过它我们可以改变指令所在组件data的任一变量。

其他方案及其缺陷

input v-model 使用 computed

export default {

data () {

return {

contentStore: ''

}

},

computed: {

content: {

get () {

return this.contentStore

},

set (val) {

this.contentStore = maxlength(val, 4) // maxlength是截取字符串方法

}

}

}

}

问题:虽然变量长度被限制了,但输入框仍可继续输入。

打印input元素的value,得到:

双向数据绑定失效了。

从v-model双向数据绑定原理入手

方法1中双向数据绑定失效了,那么我们来研究下input双向数据绑定的实现原理,看看能否找到突破口。

input的v-model只是一个简化的指令,它的双向数据绑定原理如下:

msg = e.target.value">

msg = e.target.value">

改写双向数据绑定方法:

methods: {

maxlengthInput (e) {

this.contentStore = maxlength(e.target.value, 4) // maxlength是截取字符串方法

}

}

结果和方法1一样,输入框仍可继续输入。

这里input的value是根据contentStore进行改变的,那直接修改DOM是否可行呢?

直接修改DOM的value值

在方法2的基础上,修改maxlengthInput方法,通过DOM操作来直接修改input的value,并打印出input的value进行观察:

methods: {

maxlengthInput (e) {

let value = maxlength(e.target.value, 4)

this.contentStore = value

document.querySelector('.el-input__inner').value = value

console.log(document.querySelector('.el-input__inner').value)

}

}

根据打印出来的内容可以看到,实际上input的value被改变了,而输入框内仍可正常输入,原因可能是element-ui的处理顺序问题(用原生input元素没有这个问题),那我们把dom赋值操作延后到下一个tick:

maxlengthInput (e) {

let value = maxlength(e.target.value, 4)

this.contentStore = value

setTimeout(() => {

document.querySelector('.el-input__inner').value = value

console.log(document.querySelector('.el-input__inner').value)

}, 0)

}

结果是可行的:

所以结束了吗?来输入中文试试:

中文输入法的每个拼音字母都被认为是一个有效的输入,这个缺陷是致命的。

compositionend事件

输入中文时,会先后触发compositionstart(输入中文前)、compositionend(中文输入完成后)事件。所以可以利用compositionend事件解决方法3的问题。

但是,删除输入内容时是无法触发compositionend事件的,那么,就需要同时监听input事件。

input事件会比compositionend事件先触发,所以会出现方法3的无法输入中文的问题,不过只要把input事件处理延迟到compositionend之后(比如用setTimeout),这个问题也可以解决。随之而来的问题就是输入非中文字符时,input事件处理也会延迟,用户体验不太友好。

总结

对于区分中英文来限制输入框输入长度的需求,利用input元素的maxlength属性是比较便利和可行的方式;使用vue自定义指令可以提高方法的可用性(再把指令封装成插件,可以很大地提高方法的可移植性)。

当然,vue自定义指令方法有一个问题,当在非末尾输入超过limit的内容时,会截断末尾的字符、而不是截断正在输入的内容。解决这个问题需要比较输入前和输入后的字符串、再把diff出的字符串截断掉。虽然这个问题可以解决,但性价比不高,对于当前的需求也没有解决的必要。

参考

Logo

前往低代码交流专区

更多推荐