Bootstrap

Web 键盘输入法应用开发指南 (5) —— 实战技巧

引言

在这篇文章中,我会分享一些在实际项目中遇到的问题以及常用实践供大家参考,避免踩坑。多踩坑虽然能积累经验,但也会浪费时间。与键盘和输入法相关的Web应用常常要处理平台兼容性的问题,开发者不仅要通过各种设备覆盖各个浏览器的实现,还要测试各种用户输入的场景以保证没有遗漏边缘case。其实,这类应用程序往往是通过修修补补的方式逐步完善的。本文可以在这方面提供一些参考,希望能帮助相关开发者少走弯路。

不要依赖某一种事件

从前面几篇文章可以看出,浏览器提供的、处理与用户输入相关的事件实在不少,如keydown/keypress、input以及composition等等。开发者不仅需要了解每种事件的定义和包含的信息,还要掌握各类事件触发的场景有时并不产生composition*事件,而是使用事件。这里的“有时”通常是指输入特殊符号,如中文的,,,等。

这里的例子是要提醒我们,不要依赖单一的事件。虽然composition相关事件天然是为了输入法而设计,但并不意味着所有浏览器都会触发。同理,keypress事件也经常被用来处理符号的输入,但也不保证一定存在。脑子里时刻绷着这根弦,可以避免许多因为测试不到位而暴露的严重bug(用户完全无法输入了)!

多与原生HTML页面对比

这其实是一个开发技巧。有些时候业务要求要开发者自己控制输入的过程,而非直接使用输入法软件与浏览器交互后的输入结果,比如想在根据用户输入的过程实现一些业务逻辑。如果我们对某些语言(日语、韩语)不熟悉,就不知道自己是否正确实现。这时可以另写一个简单的HTML页面,创建一个输入控件,绑定相关事件,然后对比直接在原生Web页面的输入结果。

这个方法也适用于对某些事件是否触发不太确定的场景。由于业务逻辑代码比较复杂,我们可能不好判断某个事件没有捕获到,是平台兼容性问题,还是自己实现的问题,这时一个简单的页面可能会告诉你答案。

谨慎阻止事件

阻止事件传播主要涉及两个API:[1]和[2]。使用可以取消一个事件默认的效果,但不会阻断事件的传播。比如你想阻止某个键的输入,可以先绑定事件,然后根据属性进行处理:

const textInput = document.getElementById('my-text');
textInput.addEventListener('keydown', validate, false);

function validate(evt) {
  let key = evt.key;
  if (key === 'a' || key === 'A') {
    evt.preventDefault();
    console.warn("You are not allowed to input letter 'A'.");
  }
}

这里如果遇到字母A键被按下,事件的默认行为被取消(cancelled),该键就无法输入了。与此对应,会阻止事件的捕获和冒泡过程,而不会取消事件的默认行为和效果。有关这两个API的详细用法可查阅相关文档,这里不细说。

需要注意的是,无论是取消事件效果还是阻断事件传播,都可能带来副作用,我们在使用时需要小心。比如,开发者在移动设备上取消了touch事件的效果,而是自行处理后续的逻辑,那么可能对一些输入场景产生影响。我之前在写与韩语输入法的相关程序时,踩过这类坑。

在采用韩语输入的过程中,你的设备上软键盘的输入法会有一个输入的中间状态,接下来的输入或者删除操作都与之前输过的字符相关,直到你按了回车、空格键或者单击了屏幕其他位置(触发touch事件),取消这个输入状态。问题来了,如果touch事件的效果被取消了,本来应该取消韩语输入法状态的效果没了,那么你后续的输入还是基于原来的输入状态,这就与预期不符了。感兴趣的话可以在自己的手机上试一下。

阻断事件传播也有风险,因为页面上可能有多个控件对输入事件感兴趣。如果在某个阶段阻断了事件的传播,后续输入控件无法获得相应事件,因而无法对用户输入做出响应。

注意事件触发的顺序

在前面的文章中曾经提到过,键盘、input和输入法事件的顺序不总是一定的。如果你的程序依赖这些事件的顺序,就要注意了。比如,在程序里假设keydown事件先触发(一般情况也是如此),对一些键(比如Process Key)进行特殊处理,并停止传播。接下来是composition事件触发,处理一些事输入法相关的逻辑。然而在有些移动平台上,composition总是先触发,那么keydown事件的特殊处理逻辑就漏掉了。

针对这种情况,我们在开发时可以多打一些log,观察事件在运行时的实际顺序,便于发现问题。直接在浏览器的DevTools种调试可以,不过如果断点打在事件处理程序中,停留事件过长可能导致事件失效。另外,对一些移动端的浏览器,也没有DevTools可用。

如上图,在同时在composition和keydown事件处理程序加断点,在keydown处停留后(16行),后续的断点(7行)没有进入。

移动端调试方法

其实针对移动端,还有其他调试的方法。如果移动设备浏览器无法查看console的log,可以使用一些第三方的库来截获log的输出,并以浮动UI的方式显示在页面上,供开发者查看,如腾讯开源的vConsole[3]。

我们也可以使用一些远程调试工具,将测试移动设备连接到PC上,开启设备的开发者选项,并启用USB调试功能,然后在PC的浏览器中通过DevTools调试页面。

  • 对于Android设备,以Chrome为例,可以参考官方文档[4]配置远程调试。注意,调试使用的USB线要具有传输功能,有的Type-C线只能给设备供电,却无法激活USB的调试功能。如果是三星的设备,可能还需要装驱动程序,可视具体情况查阅相关资料。Firefox有类似的调试功能,这里不赘述。

  • 对于iOS设备,以Safari为例,可以参考这个文档[5]配置远程调试。推荐使用一台Mac机调试,注意如果Safari的菜单栏没有开发(Develop)选项,需要在设置中打开。如果机器上预装的Safari不好用,也可以尝试Safari的技术预览版[6],它提供了更强大的开发者选项。

掌握多语言的知识

最后一点实战技巧就是语言知识了。有时候自己写的代码总出bug,主要是因为对相关语言的输入过程了解不够,因此忽略了很多边缘case。当然,让每个工程师掌握多种语言知识是要求过高了,但针对键盘和输入法应用的开发者来说,有意识地积累这方面知识,对提供工作效率,写出高质量代码益处良多。

举个例子,汉字是由偏旁部首组成的,但如果我们用拼音输入,是整个字一起输进去的,而不是按偏旁部首的顺序逐个部分输入(像五笔输入法一样)。然而,韩文字符就不同,它需要通过一些类似偏旁的组件组合输入:字符“조”就是由“ㅈ”和“ㅗ”拼出的,在键盘上要依次输入W和H键。而且在删除时,也要先由“조”变为“ㅈ”,再完全删除,需要按两次退格键。

像这种情况如果开发者不清楚,那么在设计测试用例时,也无法保证好的覆盖了。

总结

这一讲我分享了一些开发中常见的、有点棘手的问题或者注意事项,希望对你有所帮助。从下一讲开始,我们通过一个在线输入法的小项目来实践一下。

参考阅读

[1]

[2]

[3]

[4]

[5]

[6]

系列导航

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