Skip to main content

中文输入法输入监听优化

一、实现效果

1、优化前

使用 input 事件监控输入框内容变化时,在中文输入下,未完成选词的按键也会触发 input 事件,效果如下:

2、优化后

优化后,在中文选词完成才触发 input 事件,效果如下:

二、优化过程

.html
<input type="text" />
<span>input 的值为:</span>
.js
let curInput = document.getElementsByTagName("input")[0];
let curSpan = document.getElementsByTagName("span")[0];

curInput.addEventListener('input', (e) => {
curSpan.innerHTML = `input 的值为:${e.target.value}`
})

优化前,这里直接用 input 事件监听并将实时的值更新到 span 上。

这里使用 compositionstartcompositionend 进行组合输入监听,优化后代码如下:

let curInput = document.getElementsByTagName("input")[0];
let curSpan = document.getElementsByTagName("span")[0];

let lock = false
curInput.addEventListener('compositionstart', () => {
lock = true;
})
curInput.addEventListener('compositionend', (e) => {
curSpan.innerHTML = `input 的值为:${e.target.value}`
lock = false;
})

curInput.addEventListener('input', (e) => {
curSpan.innerHTML = `input 的值为:${e.target.value}`
!lock && curSpan.innerHTML = `input 的值为:${e.target.value}`
})

上面代码中,用一个 lock 变量来控制输入框 input 事件的监听操作行为,当组合输入开始(触发 compositionstart)时,将其设为 true,组合结束(触发 compositionend)时设为 false,同时要多执行一步 input 事件监听的操作。

当用户使用拼音输入法开始输入汉字时,compositionstart 事件就会被触发。当文本段落的组成完成或取消时, compositionend 事件将被触发。

除了 compositionstart 和 compositionend,组合输入还有个 compositionupdate 方法,当组合输入每个字符时触发,使用如下:

let curInput = document.getElementsByTagName("input")[0];
let curSpan = document.getElementsByTagName("span")[0];

curInput.addEventListener('compositionupdate', (e) => {
curSpan.innerHTML = `input 的值为:${e.target.value}`
})

上面代码在 compositionupdate 触发时用组合输入的字符对 span 文本进行更新,效果如下:

可以看到,compositionupdate 只监听使用中文输入法时的组合输入字符 nihao,最后一个组合输入的字符 o 在组合输入结束时生效,组合输入结束后不再进行输入字符 123 的监听。

三、v-model 自带输入优化

如果使用 Vue v-model 双向绑定的形式,会发现在输入中文的过程中不会触发 input 事件。

Vue 源码中可以看到这几行代码:

inserted(el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', () => {
directive.componentUpdated(el, binding, vnode)
})
} else {
setSelected(el, binding, vnode.context)
}
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd)
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true
}
}
}
}

// ...

function onCompositionStart(e) {
e.target.composing = true
}

function onCompositionEnd(e) {
// prevent triggering an input event for no reason
if (!e.target.composing) return
e.target.composing = false
trigger(e.target, 'input')
}

function trigger(el, type) {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}

可以看到,Vue 已经对 v-model 进行了组合输入的优化。