当前位置: 首页 > Web前端 > JavaScript

中文输入法中光标跟随能力触发的浏览器事件探究

时间:2023-03-27 15:58:47 JavaScript

中文输入法中光标跟随能力触发的浏览器事件探索结合事件监听机制的特点,在此记录一下填坑过程。:::模拟光标跟随大多数主流输入法都有这样的功能。输入中文时,可以使用左右方向键控制光标在输入区任意两个字符之间移动。位置,用户输入的下一个字符将直接插入到光标处。由于腾讯文档的渲染画布是完全独立实现的,为了保持与普通可编辑画布的体验一致,我们需要自己模拟光标移动行为。首先,我们需要确定的是输入法中模拟光标更新的时机。经测试,当用户输入中文时,如果用户使用方向键移动光标,会触发光标移动行为。因此,首先要解决的是使用一个合适的事件监听器来捕获这个行为并更新它。既然模拟了输入框的行为,自然而然,我们首先想到的就是输入框触发的监听器。浏览器输入框对输入的监听机制在浏览器对键盘的输入规范中,键盘输入分为直接输入和间接输入两种。直接输入会触发输入框的onInput事件(之前IE9不支持该事件,只能使用onKeyUp等键盘事件作为降级选项)。对于间接输入,规范将事件监听分为三个部分:onCompositionStart、onCompositionUpdate、onCompositionEnd。在间接输入的同时,中间状态的写入也会导致输入框的内容发生变化,同样会触发onInput事件。因此,在间接输入中,事件的触发顺序是:onCompositionStart、onCompositionUpdate、onInput、onCompositionEnd。需要注意的是,如果输入完成时输入框的内容没有变化,则不会触发onChange事件和onCompositionEnd事件。中文输入法是在打字选词过程中的一种间接输入。这时候,中间文本就不会直接放在输入框里了。按回车或其他键退出中文输入选词后,中文文本会放在输入框中,此时是直接输入的情况。我们需要注意的游标事件,显然是在间接输入中得到的。当输入法选词光标左右移动时,由于内容不变,此时不会触发onInput事件,但会触发一次onCompositionUpdate事件。我们可以通过这个事件来判断光标位置,重新设置光标在画布上的位置。但是最终我们并没有以这个事件作为评判,原因会在下面说到。判断当前光标的位置解决了重置光标的时机,接下来就是判断光标位置的时候了。由于DOM标准中没有直接获取光标位置的方法,这部分也需要我们自己实现。我的想法是,通过选择从光标到输入起始位置的字符串,判断所选字符串的长度,就可以知道光标当前位置相对于起始位置的偏移量,从而确定位置光标。对于普通的input输入框,启动比较简单。输入框提供了inputElement.selectionStart属性作为当前光标位置和输入起点的偏移量,我们可以直接使用。但是contentEditable=true的div节点就没有这个属性,我们得另辟蹊径。根据之前写E2E测试的灵感,我们可以模拟创建一个从当前光标位置到输入开始位置的选区,判断选区的字符串长度为光标位置的偏移量。Selection对象可以通过window.getSelection()方法获取,它是一个代表当前文本选择区域的对象。由于我们处于输入状态,选择区域的位置是在当前输入框中,这样我们就可以得到上面需要的偏移量。constselection=window.getSelection();//确认输入框处于输入状态且有选择区域if(selection.rangeCount>0){constrange=selection.getRangeAt(0);returnrange.endOffset;}获得光标位置后,仍然需要将其重置回我们的画布上。setting的思路其实也差不多。通过document.createRange方法创建一个选择范围,将其起始位置设置为需要移动的目标位置,然后移除选择区域,使光标落在目标位置上。性能优化说当光标移动时,确实会触发onCompositionUpdate事件。但是onCompositionUpdate事件是高频操作,每次间接输入都会触发,会导致光标不断重置位置,造成不必要的性能损失。而且onCompositionUpdate事件的入参只有更新后的中间串值,只能用来判断输入的中间串是否发生变化。移动游标的行为本身不会导致字符串发生变化,但是反过来说,使字符串不发生变化的操作一定是移动游标的操作的说法就不成立了。因此,虽然移动光标会触发该事件,但是我们仍然没有有效的手段来判断该事件是否是由输入法中光标的移动触发的。好吧,我花了很多时间讲了光标变化的本质其实就是选择区域的变化。那么,输入法触发的光标移动是否会向输入框发送选择区域变化通知呢?不幸的是,目前大多数输入法都不支持。并且由于光标移动被认为是输入法的内部行为,因此光标在输入框内移动时不会主动抛出任何事件。所以无法触发输入框选择改变事件的onSelectionChange事件。由于输入框的事件监听无法准确判断光标的移动,我们只能退而求其次,从更底层的逻辑上监听键盘按键输入来尝试恢复这种行为。优化思路如下。触发光标跟随的时序规则是:当用户输入时,如果用户使用左方向键移动光标,则启用光标跟随能力,光标位置会随着输入不断更新直到光标再次移动到结束位置结束。由于中文输入时按左方向键的行为属于低频操作,这样一来,大部分输入操作都不需要进行判断和重置光标,提高了正常输入下的性能。附上最终的判断逻辑:那么,用户输入的关键信息如何获取和判断呢?当然是使用一级事件接口KeyboardEvent。中文输入法的键盘输入事件支持KeyboardEvent是在底层提示用户与某个键盘按键的交互是什么,不涉及该交互的上下文含义。一般来说,当你需要处理文本输入时,应该改用上一节提到的输入框监听事件。例如,当用户使用其他方式输入文本时,例如平板电脑的手写系统,可能不会触发键盘事件。KeyboardEvent对象描述了用户与键盘的交互。每个事件都描述了与某个键(或某个键和修改键的组合)的单个用户交互;事件类型keydown、keypress和keyup用于标识不同类型的键盘活动。键盘输入事件的设计思路和间接输入钩子类似。在浏览器中,键盘输入也分为onKeyDown、onKeyPress、onKeyUp事件触发三个阶段,分别对应按键不同的行为触发时机。(注意:onKeyPress事件高度依赖设备支持,所以尽量不要使用这个hook)这三个事件都是作为KeyboardEvent的参数传入的,有助于我们了解事件当前执行时触发的按键信息。MDN上的入参有如下属性支持:在文档规范中,我们可以找到很多新的属性,对解决问题很有用,比如event.isComposing属性用来判断当前是否触发onCompositionUpdate事件,而event.code是用来判断当前按键输入的,与键盘布局和输入状态无关,很容易获取中文输入中的按键。我们可以利用这两种状态来帮助我们完成按键监听和事件触发。前面说过,KeyboardEvent是一个非常依赖硬件和软件支持的事件。它不仅需要浏览器的能力支持,还与输入法甚至键盘类型有关系。经过实验发现,在很多浏览器和输入法的组合中,这些新属性都无法通过onKeyDown正确获取,Windows下的部分中文输入法甚至无法支持event.key属性。为了实现最大的兼容性,在自下而上的方法中,只能使用已被弃用的event.keyCode代替。使用自下而上计划的问题是否已经解决?一点也不。输入的汉语拼音中字,系统无法识别。在Windows桌面应用的键盘输入规范中,我们发现Windows将所有无法识别的设备输入设置为VK_PROCESSKEY229,浏览器的event.keyCode复用了这个规范,所以在中文输入过程中,无论是什么键,返回的event.keyCode一直是229,网上解决这个问题的方法是使用onKeyUp,而不是onKeyDown。但是首先,这不符合需要实时反映输入的光标移动操作的要求。其次,使用onKeyUp时会出现较多的问题。Windows下进行中文输入时,由于不同输入法回调onKeyUp的实现不同,这个事件可能会触发一次或两次,要么全229,要么229一次,另一次是正确的键(没错,就是你,搜狗).为了避免我们不断地填各种第三方输入法实现的坑,自下而上的方案采用了在检测到无法识别的按键时让光标跟随的能力。结束语经过一组操作,完美实现了这套中文输入法下的跟随光标功能。回头看看我们为解决这个问题走过的坑,其实反映了浏览器JSDOM标准在不断演进,不断弥补历史遗留的坑。当然,它还远非完美,还有很多能力缺失。比如我们在这道题中遇到的判断游标偏移量的解法,本质上就是hack。而扩展JS的能力边界,使其更强大、更易用,正是我们作为前端开发者需要努力的方向。