经常有需求,在限制文本框输入长度的时候,区分中英文,如:中文(全角符号类似)占两个字节,英文则占一个,最近审查代码,发现项目上存在类似需求,组内同学实现了网上经常提到的方案,输入后替换,由于监听的是input事件,导致任何越界输入都会出现闪烁的情况,不同于原生效果。

回忆到以前曾经用keydown结合其他事件实现了一版,晚上没事,就尝试重新实现了一下,由于目前项目不考虑非webkit内核浏览器,只在chrome下简单测试了下,有兼容性问题欢迎评论告知

const keyCode = [37, 38, 39, 40, 91, 8, 46, 34, 12, 20]
const specialKeyCode = [65, 67, 88]


function getLength(value) {
    return value.replace(/[^ -~]/g, '00').length
}

function slice(value, maxlength) {
    if (maxlength && maxlength > -1) {
        while (getLength(value) > maxlength) {
            value = value.slice(0, value.length - 1)
        }
    }
    return value
}

function checkKeydown(e) {
    const code = e.keyCode
    return !((e.metaKey || e.ctrlKey) && specialKeyCode.includes(code)) && !keyCode.includes(code)
}

function limit(target, targetValue) {
    const value = targetValue || target.value
    const maxLength = target.maxLength
    let newValue = value
    if (maxLength > -1 && getLength(newValue) >= maxLength) {
        newValue = slice(newValue, maxLength)
    }
    if (newValue !== value) {
        target.value = newValue
        target.focus()
    }
}

function compositionEnd(e) {
    const target = e.target
    const maxLength = target.maxLength
    if (maxLength > -1 && getLength(target.value) > maxLength) {
        limit(target)
    }
}

function limitMaxLength(input) {
    input.addEventListener('keydown', e => {
        const target = e.target
        const maxLength = target.maxLength
        const value = target.value
        if (maxLength > -1 && getLength(value) >= maxLength && checkKeydown(e)) {
            e.preventDefault()
            e.stopPropagation()
        }
    })
    input.addEventListener('input', e => {
        if (!e.isComposing) {
            compositionEnd(e)
        }
    })
    input.addEventListener('compositionend', compositionEnd)
    input.addEventListener('paste', e => {
        e.preventDefault()
        e.stopPropagation()
        const target = e.target
        const insert = e.clipboardData.getData('text/plain')
        const selectionStart = target.selectionStart
        const value = target.value
        limit(target, value.slice(0, selectionStart) + insert + value.slice(selectionStart))
    })
}

limitMaxLength(document.getElementById('input'))