input事件中文触发多次问题研究
我们在网页中经常会遇到实时搜索的情况,或者其他类似需要input实时响应的问题,一般情况下,我们是利用input和propertychange事件来监听input内容的变化来响应,但是有一个问题就是当输入汉字的时候,可能我们要输入 ‘实时’ 的时候,我们的input框中会出现 \’shishi\’直到我们的空格才会变成 \’实时\’,这也就意味着我们依次响应了 \’s\’,\’sh\’,\’shi\’,\’shis\’,\’shish\’,\’shishi\’,\’实时\’,前面的结果明显不是我们需要的 ,造成了我们很多次无用的提交,如果是接口请求,那更要命,多发了好多次请求。
最早之前有一个稍微能改善的解决方案就是配合一个定时器延时执行,这样能减少请求次数,但是这个减少是不分情况的减少 ,还是治标不治本。
今天偶然看到几个事件,发现可以完美解决这种问题。我们来看一下这几个事件
compositionstart ,
compositionupdate
,compositionend
compositionstart 官方解释 : 触发于一段文字的输入之前(类似于 keydown
事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词),通俗点,假如我们要输入一段中文,当我们按下第一个字母的时候触发 。
相应的compositionupdate在我们中文开始输入到结束完成的每一次keyup触发。
而compositionend则在我们完成当前中文的输入触发 。
正题来了,通过上面的事件我们就可以完美的解决中文输入的响应问题了,从compositionstart触发开始,意味着中文输入的开始且还没完成,所以此时我们不需要做出响应,在compositionend触发时,表示中文输入完成,这时我们可以做相应事件的处理。
所以我们可以设置一个变量,或者给input定义一个属性,在compositionstart到compositionend之间对input事件不做出响应。看以下代码
$(\'input\').on({ input : function(e){ var flag = e.target.isNeedPrevent; if(flag) return; response() }, compositionstart : function(e){ e.target.isNeedPrevent = true ; }, compositionend : function(e){ e.target.isNeedPrevent = false; } }) function response(){ $(\'div\').append(\'<p>事 件触发</p>\') }
我们通过compositionstart,compositionend事件来设置flag,判断是否正在进行输入中文以控制input事件的响应,看上去没有问题,但实际执行时会发现在谷歌浏览器中input执行顺序要先于compositionend,火狐执行顺序正常,但compositionend会响应两次。这就导致谷歌浏览器中输入汉字不会响应input事件。当然也可以在compositionend事件中再执行一次response事件,这样的问题是在火狐浏览器中会多执行一次response,显然不是最优方案。
经过试验,发现keyup和compositionend事件执行顺序在各大浏览器都保持一致,于是我们改成如下代码:
$(\'input\').on({ keyup : function(e){ var flag = e.target.isNeedPrevent; if(flag) return; response() }, compositionstart : function(e){ e.target.isNeedPrevent = true ; }, compositionend : function(e){ e.target.isNeedPrevent = false; } }) function response(){ $(\'div\').append(\'<p>事 件触发</p>\') }
这样在各个浏览器基本保持一致了(兼容compositionstart的浏览器)。但是keyup有一个问题,比如通过鼠标复制粘贴的时候并不相应keyup事件,所以上面的事情我们还需要再优化下,keyup相应按键事件,input响应除了keyup之外的变化事件。代码如下
$(\'input\').on({ keyup : function(e){ var flag = e.target.isNeedPrevent; if(flag) return; response() ; e.target.keyEvent = false ; }, keydown : function(e){ e.target.keyEvent = true ; }, input : function(e){ if(!e.target.keyEvent){ response() } }, compositionstart : function(e){ e.target.isNeedPrevent = true ; }, compositionend : function(e){ e.target.isNeedPrevent = false; } }) function response(){ $(\'div\').append(\'<p>事 件触发</p>\') }
从目前需求可以完全实现了。
参考文章:
https://developer.mozilla.org/zh-CN/docs/Web/Events/compositionstart