在这篇文章中,我们开始探讨浏览器对输入法(Input Method Editor, IME)[1]相关事件的支持。我们经常使用中文,因此对输入法并不陌生。事实上,输入法只有在中文、日文和韩文等少数语言中有用,大部分欧美人可能都没有输入法的概念。不过考虑到使用输入法的人群的绝对数量还是很大的,各大浏览器厂商对输入法事件的支持也相对完善。
以中文拼音输入法为例,输入的过程大致可以分为组字(composition)和提交(commit)两阶段。比如我们想打“你好”两个字,会在输入框输入“nihao”的拼音,当输入第一个字母“n”时,组字过程就开始了。此时本地的 IME 软件(比如微软拼音输入法)会为我们提供组字框和候选列表的 UI 组件,如下图。
输入法组件
compositionstart
[3]。该事件表明组字阶段的开始,我们在 Web 应用程序中可以通过此事件识别用户是否开始使用输入法了。
另外,在一些带口音(accent)的欧洲语言中,有时候也需要输入法去拼出完整的口音字符,因为如果使用的是英文键盘,可能没有口音符号键来充当 Dead Key,因此需要一个组字过程完成输入。
需要注意的一点是,如果仅仅是在标准的 HTML 可编辑控件中完成各类语言的输入,各种平台的浏览器已经做的很好了,不需要开发人员做过多的工作。只有当你的 Web 应用程序需要干预用户的输入过程和结果时,才需要关注具体的键盘和输入法事件。比如你想做一款在线的编辑器、输入法或者将输入定向到远端等功能特性,这些事件尤其有用。
compositionstart
、compositionupdate
和compositionend
是一组事件[4]。首先是 start 被触发,组字框和候选列表相应出现;此后,每按一个新键,就会触发 update,此时组字框和候选列表的内容也发生变化;当选择了候选列表中的某个字或词,或者敲击空格(中文输入法),end 事件会触发,表明输入被提交。
在一些语言的输入法中有特殊的功能键。比如日文输入法,在开始输入后需要敲击空格键调出候选列表,此时的空格键并不是正常的空格键(keyCode
为 32,key 为“ ”),而是被浏览器解释为 Process Key(Windows 上keyCode
为 229, key
为“Process”)。实际上,用于提交输入的空格和回车键、从候选列表中选择目标字符的上下箭头键,以及调整光标位置的左右箭头键,也都是一种 Process Key。这样应用程序就可以区分正常的按键和在使用输入法过程中的功能按键。
在输入过程的任何时候如果输入回车键,就会提交输入,compositionend
事件也会触发。对于日文输入法,就是提交当前选中的字符;而对于中文输入法,则会提交组字框中的拼音。在使用输入法的过程中,也可以取消输入,一般有两种方式:一是用户的操作,比如使用鼠标单击页面空白处,就会终止当前输入;二是在compositionstart
之后,通过preventDefault
方法阻止后续事件发生[5]。但无论何种情况,compositionend
事件都会触发。
通常情况下,compositionstart
会开启一个组字的会话(session),然后会有一个或者多个compositionupdate
事件描述输入的过程,最后随着compositionend
事件提交输入结果,结束组字会话。在这期间,每次compositionupdate
事件触发,都意味着 DOM 马上要更新为最新的字符,DOM 更新结束后还会紧跟着一个input
事件,表示当前字符更新成功。不过还是那句话,此为标准中定义的事件序列,并不是所有浏览器都如此实现。
在一个会话中,每一次非打印字符的输入都被视为Process
键。比如在日文输入法中使用空格来调出候选列表,并选择候选字词;在中文输入法中使用回车来提交当前输入;在韩文输入法中使用 Hanja 键来切换到汉字输入等等。这些 Process 键的 keydown 或者 keyup 事件中,isComposing
属性的值都是true
,表示输入未结束,这对应用程序来说可能是个有用的属性。随着会话结束, 在 compositionend 事件触发以后,从最后一个 Process 键(比如回车键)的 keyup 事件开始,isComposing
就变为false
。
值得一提的是,在某些输入法中有selection
的概念,即输入过程支持分段组字。如下图中的日语输入法,正在拼写的是“地”这个字符,候选列表里有多个选项。此时使用左右方向键可以切换拼写的目标(即一个 selection),比如切换到右边的“安气”,每一段都有特定的 UI,如下划线。具体几个字一组与输入的语言相关,由输入法自行决定。同时,在浏览器中会有compositionupdate
事件产生。不过通过compositionupdate
,我们也只能获取当前正在拼写的字符或单词,没有更具体的比如光标位置、selection 范围的信息了,而这些信息通常在一些 PC(如 Windows)上输入法相关的接口中是可以获取的。
日语输入法分段组字
一般在组字的过程中还会触发input
事件[6]。input
事件意味着 DOM 正在被更新,这里更新可以是键盘输入写入编辑区域、删除内容或者格式化文本等。一般情况下,我们通过 composition 相关事件就可以拿到当前的输入状态,但有的时候还是要依赖 input 事件,尤其是在移动端平台。在 Android 的 Chrome 实现中,对于中文输入法就不会触发 composition 事件,我的理解是在页面中输入时,Android 会生产自己的组字框和候选列表,并放置在软键盘的上方,相当于 composition 过程完全由系统处理,并与浏览器交互,开发者不用参与。
Android 中文输入法UI
然而,有时候我们的确需要知道 composition 的过程,那么就可以使用input
事件,其data
属性的值与 composition 相关事件的data
属性值一致。iOS 平台也有类似的实现。不过由于 composition 和 input 事件可能都会触发,在监听事件并处理时要注意去重,即不要对相同的事件数据作二次处理。
在使用输入法的过程中,input
事件的isComposing
属性也为true
。另一个需要注意的属性是inputType
[7],它描述了触发 input 事件的操作如何修改了编辑区域(如 input 控件)。举个例子,如果使用中文输入法,在 compositionstart 之后,每个 compositionupdate 都会伴随一个 input 事件,该事件的inputType
属性值为insertCompositionText
,表示将当前正在拼写的内容替换为最新的值。
又比如在 iOS 的 Safari 浏览器中,使用韩语输入法输入“alal”的序列,首先“ala”会拼出韩语字符“밈”,接下来的“l”会将前面的字符下方的“ㅁ”拆分出来,并与新的字符结合使得结果变为“미미”。在这个拆分的过程中会出现一个 input 事件,其inputType
属性值为deleteContentBackward
,表示删除当前光标前的内容。注意,带有这个属性的 input 事件在 Windows 上就不会出现,在开发时需要考虑兼容性。参考阅读[7]有一个详细的 inputType 列表,可以根据需要使用。根据我的经验,这个列表里的很多事件只在极少数浏览器中使用,且与输入的语言也有关系。不过一旦被使用,则可以精确监控输入的过程。
从参考阅读的资料可以看出,很多所谓的标准其实还是一个草稿(Draft),并且区分了不同级别(Level)。虽然时 W3C 组织起草的标准,但不意味着所有浏览器厂商都会严格标准实现,这就带来了许多兼容性问题。从我的开发经验来看,Chrome 浏览器以及移动端的浏览器的实现更特殊一些,相比之下 Firefox 和 Safari 更接近标准。从这里的文档[8]可以看出,Chrome 只实现了 Level1 的标准,而 Safari 同时实现了 Level1 和 Level2 的标准,二者的区别可以仔细阅读上述 Markdown 文档。
W3C Input Events Spec
这篇文章系统梳理了浏览器对输入法相关事件的支持的技术要点和注意事项,在开发相关应用时依然要注意平台和浏览器的兼容性问题,尽量在多种平台做足测试,防止输入体验出现问题。由于涉及不同的输入语言、输入法、浏览器以及平台,很多技术细节比较琐碎,因此不要一口消化,要注意经验的积累,并做好回归测试。下篇文章将会介绍快捷键相关的内容。
[6] Input Events
[8] W3C Input Events
领取专属 10元无门槛券
私享最新 技术干货