Web 键盘输入法应用开发指南(9)——标准与实现
引言
这篇文章中,我们通过两个特别的主题来探讨标准与实现的问题。在 Web 前端领域,虽然标准总是先行,但浏览器厂商以及各平台是否跟进,却有着自己的考量。在前面的文章中,我们已经多次提到过具体的案例,本文中案例一个是关于特殊的keyCode
属性[1],另一个是关于键盘布局 API 的支持。
虽然keyCode
属性已被标准废弃,且不推荐使用,在一些老旧系统中,这个属性依然在发挥作用,有些特殊情况还是要处理。关于键盘布局(Keyboard Layout),在这个系列的第一篇文章中就简单提及。因为这是一个涉及用户本地设备的数据隐私的 API,浏览器迟迟没有提供较好的支持,后面会详细说明。
神秘的 keyCode 229
有的时候,我们会遇到keyCode
属性值总是为229
的情况。一般有两个原因:一是在输入法启用的状态,每个按键都用于输入法的组字过程,此时keyCode
为 229;二是在安卓系统上使用 Chrome 浏览器,当使用软键盘输入时,按键的keyCode
始终为 229[2]。其实keyCode
的值应该设置什么时有章可循的,比如可以参考 W3C 的标准文档[3]。这里面有几个关键的点:
从操作系统的事件的虚拟键值(virtual key code)[4]读取
操作系统一般都有虚拟键的定义,比如在 Windows 上,针对某个应用程序的窗口,你可以使用类似 Spy++的软件,抓取发送给这个窗口的按键事件,其中就有以VK_
开头的属性,那就是虚拟键值。例如,虚拟键VK_MENU
代表 ALT 键,其值为0x12
。
虚拟键来自操作系统对键盘输入的处理,但是浏览器不一定能得到全部的信息,这个跟平台的实现相关。在 Windows 等系统上,键盘的事件还可能被输入法等软件“吃掉”,而无法到达浏览器端。因此,只凭借虚拟键设置keyCode
并不可靠。
如果是输入法处理按键,且类型是 keydown,则返回 229
因此对于输入法启用时,keyCode
值为 229 是标准规定的。但不同的平台和浏览器出于各种考虑,不一定有相同的实现。对于安卓系统的情况就是一个例子,在使用软键盘时,安卓可能认为就是一种输入法的特殊情况,因此没有向浏览器发送全部按键信息,而总是返回 229。况且真正输入的字符也可以通过其他事件获取到,不强依赖于keyCode
属性。关于这种实现是不是一个 bug 有了很多讨论,感兴趣的话你可以查看这个帖子[5]。
注意,这里针对的是 keydown 和 keyup 事件,如果是 keypress,则另有规则。keypress 事件的keyCode
经常被设置成对应字符的 Uncode 码点(code point),这在移动端经常用到,在 PC 端通过 composition 事件拿到的输入数据,在移动端可能要依赖 keypress。
如果是数字和字母键,那么
keyCode
就是对应的大写字母的 ASCII 码
这就是为什么字母 a 的keyCode
为 65,而不是 97,keyCode
并不包含修饰键的信息,比如结合 SHIFT、CapsLock 等。接下来还有一些设置的规则,这里不一一列举,如果到最后没有合适的keyCode
,那么就设置为 0。
键盘布局 API
关于键盘布局的 API,我在第一篇文章中简单提及,那就是还未被广泛支持的navigator.keyboard.getLayoutMap
方法[6]。它可以获得本地键盘布局的信息,通过 Promise 返回。这个方法未被广泛支持的原因是它在一定程度上违反了用户设备数据的隐私性(Privacy)。
关于隐私性我的理解是,浏览器不能在用户没有感知的情况下,读取本地设备上的敏感信息。这里有几个关键点,首先是用户没有感知,如果涉及了用户交互,比如征求用户同意,且用户可以选择接受或者拒绝,就不算违反隐私性。然后是读取这个操作,这是相对于修改、篡改而言的,后者就不仅仅是隐私性问题了,而是归为安全性(Security)问题。比如,我们上篇文章提到的禁止通过脚本修改设备上软键盘的信息,就涉及安全性考虑。
最后一点是"敏感信息"的定义。我们通常可以理解为与用户的痕迹、习惯、偏好相关的信息,但也不是太明确。你认为本地使用的键盘布局是敏感信息,别人可能觉得无所谓。不过目前看来,键盘布局的 API 迟迟没有被浏览器广泛支持,还是因为认可它的隐私性的。这里有个微软关于该 API 的需求的消息[7]。
这个 API 之所以有需求,是因为它在某些场景下带来了用户体验的提升。比如,应用程序可以根据当前的系统的键盘布局的变化,切换处理输入的逻辑,让实现更高效,更契合当前输入的语言和键盘布局的特点。由于目前开发者无法拿到这类信息,因此对多种语言和键盘的兼容性显得力不从心。下面我们通过代码来看看这个 API 的用法。
首先可以通过caniuse网站查看该 API 的兼容性,我直接使用最新版本的 Chrome(如 v99)。其次,你的测试程序要通过安全连接访问(如 HTTPS)。如果连接是非安全的,Chrome 浏览器中该 API 依然是不可见的。因此,你可以使用本地文件系统打开 HTML 文件;如果通过 HTTP 访问,则需要使用 localhost 作为域名,不要使用 IP,因为 localhost 也会被认为是基本安全的。
任何时候,可以通过navigator.keyboard.getLayoutMap()
获得一个从字符 code 到 key 的 map(keyboardLayoutMap
),即给定一个键位(code)的信息,这个 map 可以返回在当前键盘布局下,产生什么字符输出(key)。有了这个 map,我们可以通过尝试一些比较特殊的键位,从而推断出键盘布局是什么。比如,我们事先定义了一个charToLayouts
的表:
通过逐一尝试表中的每一项,将key
字段传递给keyboardLayoutMap
,看输出是否是value
对应的字符。如果是,则推断出了当前的键盘布局。比如数字 2 键位的返回值是字符é
,说明当前键盘布局是法语键盘。这种实现目前还有两个限制:
API 返回的 map 不包含所有键的映射,目前只使用可打印字符(比如,字母、数字和符号等)
没有相应的事件通知键盘布局的变化
关于第二点,其实相关标准[8]里面已经提供了layoutchange
事件,只不过包括 Chrome 在内的所有浏览器都没有实现它。如果没有这个事件,我们应用程序不好决定何时进行上述的键盘布局检测。我个人觉得,这个 API 未来被全面支持的希望不大,可能会默认关闭,然后可以通过浏览器的某种隐私设置打开它。
总结
本文借两个案例探讨了标准与实现的问题。各个平台和浏览器厂商对某个 API 或者语言特性的支持,需要从向前兼容性、平台特性、实现复杂性、隐私与安全性以及未来的可能性等多个方面综合考虑,因此不一定完全按照标准来实现。这不仅体现在键盘和输入法相关的实现中,也是前端开发的常见问题。当遇到实现与标准不一致时,搞清楚为什么会这样,可能会给我们理解所使用的技术甚至业务带来启发。
参考阅读
[1] keyCode property
[3] keyCode Spec
[5] Discussions on Android Chrome keyCode
[7] Microsoft'S requirement on getLayoutMap API
系列导航
如果您对这个系列感兴趣,可以通过下面的导航找到对应文章👇🏻。
版权声明: 本文为 InfoQ 作者【天择】的原创文章。
原文链接:【http://xie.infoq.cn/article/6eb4111dab4545c5fd45aac60】。文章转载请联系作者。
评论