Bootstrap

Web 键盘输入法应用开发指南 (4) —— 组合键

介绍

在Web应用程序中,组合键也是一种常用的功能,比如复制-剪切-粘贴的系统功能,ALT+F打开文件菜单的浏览器功能,以及一些自定义的组合键(比如Ctrl+Shift+A完成应用程序的某个操作)。更广义的组合键还包括在某些欧洲键盘布局上,一些特殊字符的输入方式。例如在Mac OS的英语键盘上,可以通过组合键输出苹果Logo字符(这里没有写出该字符是因为,在Windows上没有苹果的字体,无法正确显示该Unicode字符)。在这篇文章中我们来看看组合键的处理实践。

事件处理

一般来说,涉及系统的快捷键,都会被系统首先截获并处理,然后才会发送给浏览器,触发相应的按键事件进行处理。我们这里关注的就是浏览器通过事件提供给应用程序的这一部分,如果发现浏览器中有事件的缺失,首先要考虑操作系统的实现,是否为了某种目的少发了一些事件,比如Windows 10系统2004以后的版本可能会有此问题,又是ALT/SHIFT/CTRL组合键的keyup事件会被丢掉)。

然后再考虑是否是输入法等系统软件吃掉了一些事件,比如有些快捷键恰好能被输入法响应(如Ctrl+Space等);再考虑浏览器在当前系统上的实现,比如一些Linux系统上的Firefox浏览器与Windows的实现有较大差异。排除了这些兼容性问题后,我们就可以把精力放在应用程序对事件的处理上了。

常规组合键

我们先来看常规组合键。以为例,如果想在用户按住ALT键然后按下SHIFT键时做一些事情,通常的做法是:先捕获ALT的事件,然后记住ALT被按下的状态后马上返回;等到SHIFT的keydown事件到达时,判断SHIFT按键事件的是否为(是否按住了ALT键);如果为,则满足期待的条件,进行下一步处理。

if(event.keyCode == 18){
    altDownEvent = event;
    altFlag = true;
    return true;
}
...

if(event.keyCode !== 18 && altFlag) {
    processHotKeys(altDownEvent, event);
    altDownEvent = null;
    altFlag = false;
}

如代码所示,当前事件(event)为ALT触发时(),记住当前的事件对象,然后返回,表示让事件继续传播,保持默认行为,可能有其他的事件处理器需要。在接下来的某个按键(如SHIFT)事件触发时,一同处理刚才的ALT和这个键,并清空刚才的ALT状态。这样我们就是知道是组合键生效了,而不是单独按了ALT键或者其他键。当然,如果ALT+SHIFT后面还有键,比如一个字母键作为组合键,则对SHIFT也要做类似处理,并在字母键到达时,判断ALT和SHIFT的状态。

需要注意的是,一些组合键会被系统上的键盘或输入法框架首先捕获,如Windows上的,以及Mac上的,都可以切换系统当前的输入法或者键盘,然后才被浏览器处理

我们的Web应用程序可能要避开这些系统的组合键,因此要对这类组合键事件选择性地忽略。要做到这一点,也要首先识别出组合键,方法与上面类似,然后直到用户的输入不满足组合键的模式时,才继续处理。

比如我们要忽略ALT+SHIFT,那么在ALT键按下时我们不能进行常规处理,而是要阻塞住并等待后续按键。当SHIFT按下时,我们依然不能常规处理,直到用户后面又按下了字母键F,前面阻塞的ALT和SHIFT才会继续处理。此时应用程序认为用户按的是ALT+SHIT+F的序列,而不是ALT+SHIFT的组合键,因此要解除阻塞正常处理。这里面涉及的逻辑很容易出bug,要多调试多测试,原则就是既能保证目标组合键被忽略,又不破坏正常的按键序列。

在某些欧洲语言中(比如法语),一些字符(货币符号、口音符号等)需要键[1]配合完成输入。AltGraph是一种修饰符(modifier)键,在Windows上,该键就是键盘右侧的ALT键。对于系统本身来说,在组合键中使用左边还是右边的ALT键效果是类似的。

但到了浏览器环境就不一样了,一些浏览器会将右边ALT键转换为一个CTRL键加上一个左边的ALT键的组合。这会增加我们应用程序的处理复杂性,不过对于支持多语言输入的应用程序来说,这也是必须考虑的一个点。

修饰符键

正如前文所述,一些欧洲语言中的字符需要借助AltGraph或者Option等功能键组合完成输入,这类组合键严格意义来说并不是真的组合键,因为它完成的还是输入字符本身,而不是一些功能性的行为。对于这类组合键,浏览器的一般会使用事件获取输入的结果。

比如在Mac OS上,当输入会产生这个字符。在对应的keypress事件中,属性的值为“KeyP”,表示字母P这个位置的键被按下了;属性的值为“π”,表示最终的输入结果。关于key和code属性的详情,可以参考这个系列的

有时,组合键的输出结果还有CapsLock键的状态相关,我们可以通过event对象上的方法来检查这个状态[2]。

总结

涉及组合键的处理通常比较复杂,要想消除bug需要尽量考虑多语言、多平台的兼容性,多做回归测试。另外,有些组合键的顺序也比较关键,哪个键先按下,哪个键先释放,都对处理逻辑有影响,这些都需要经验的积累。下一讲我会分享一些与实践相关的技巧。

参考阅读

[1]

[2]

系列导航

如果您对这个系列感兴趣,可以通过下面的导航找到对应文章👇🏻。