检测 TextArea 的改动部分

对于一个代码编辑器,例如 LaTeX 或者 Markdown 编辑器,如果想提供即时预览,就需要检测 TextArea 的改动部分。要是代码不长,在内容变动时逐字符比较 textarea 中的新旧内容即可。要是代码有几万个字符的长度,这样做效率就太低了。我们需要更方便的方法。

前面已经说过,textarea 的当前光标选择范围是可以得知的。而 textarea 的内容变化,基本上是如下这些事件引起的:键盘事件(插入、删除),鼠标事件(选择、拖放),剪切板事件(剪切、粘贴)。在事件处理程序中,我们可以尝试用下面两种方法计算出 textarea 的改动部分:

  1. 从原来的光标选择范围以及所输入或粘贴的文本。
  2. 从原来的光标选择范围以及现在的光标选择范围。

第一种方法需要处理各种情形,比较复杂。因此我们选择第二种方法。用这种方法,关键在于分清哪些情形应该处理,哪些情形应该忽略。

为使光标范围的每次变化都可以捕捉到,至少需要监听如下这些事件:keydown,keyup,mousedown,mousemove,mouseup,cut,paste。其中使用 keydown 和 keyup 而不使用 keypress 是因为粘贴时不会触发该事件,而使用 mousedown 和 mousemove 是选择文本时会触发的事件。

再看需要忽略部分。如下这些情形的光标范围变化只需记录下来,实际并没有内容变化:

  • textarea 无论是否在焦点的 mousedown 事件 
  • textarea 不在焦点时的 mousemove 事件
  • textarea 在焦点但是光标范围没变化时的 mousemove 事件

用鼠标拖动选择文本的情形也需要处理好。这种情形和用鼠标选择文本的情形有些相似,它们同样是触发了 mousedown 和 mousemove 事件(后者最后还触发 mouseup 事件但前者没有)。但是前者有内容改动和后者没有改动。在大部分浏览器中,我们可以根据是否有选择文本来区分这两者。但 Firefox 有些特殊,拖动选择文本结束后会取消文本选择状态,将光标停留在选择文本的最后面。这样导致这两者有时无法区分,只能扩大范围进行字符比较。

最复杂的问题是中文输入。中文输入开始时可以从 keydown 事件的 keycode = 229 或者 197 得知,但是结束时没法得知。另外,Chrome 会将输入法的候选文本也放在 textarea 中,而且输入时用鼠标点击候选词也可以完成输入。这种种问题导致要从各种事件中判断文本的修改非常困难。我们可以借鉴 CodeMirror 2 的处理方式:在开始输入文本时记录位置,在按控制键或者点击鼠标或者失去焦点时重置该位置。然后每次计算改动时都从该位置开始比较。

参考资料:
[1] JavaScript Madness: Keyboard Events
[2] Javascript Madness: Mouse Events
[3] oninput event | input event JavaScript
[4] textInput event JavaScript
[5] ime-mode - MDN
[6] Detecting IME input before enter pressed in Javascript
[7] how to detect multi-byte characters inputting end by javascript?
[8] 在 JavaScript 中监听 IME 键盘输入事件 - Cat Chen
[9] 中文输入法无法触发onkeyup事件的问题
[A] DOM3 Events Keyflow from Olli Pettay on 2008-04-02
[B] 从谷歌的一个Bug说起,谈谈键盘事件的兼容性
[C] CodeMirror: Internals

原文地址:https://www.cnblogs.com/zoho/p/2578885.html