前言

要带有特殊场景的,可以先有个印象。今日前端早读课文章由蚂蚁金服@茶山小旋风分享。

正文从这开始~~

先介绍下大背景网页搜索快捷键,我们是 Ant Codespaces 团队,主要为蚂蚁的工程师们提供云上研发能力。我们对外透出的是一个典型的 B/S ,用户无需在本地下载任何软件,在浏览器中就能完成标准技术栈场景下的研发工作。

于此同时问题随之而来。

应用 相比于 Native 应用 存在一个显著的缺点:键盘事件会冲突,现象是部分快捷键组合在 Web 应用中会 “失效”,举几个例子:如 Cmd + W、 Cmd + N 、 Cmd + T 等等组合,这些事件不做额外处理是无法被 Web 应用所正常响应的。

究其原因,可以归于 “Web App 还未来得及处理 Keyborads Event,Browser 已经做出了对应的响应并产生了副作用”,比如当 Cmd + W 被浏览器响应后,其副作用为关闭当前 tab 页,页面都被关闭了,Web App 自然也就被关了。

Ant Codespaces 作为研发上云的重要组成部分,而快捷键又是工具产品提效的重要方式之一,因此在快捷键这件事上我们需要给用户提供不落后于本地 IDE 的按键组合功能与体验。

既然我们遇到了这个问题,那么友商是怎么做的?Github Codespaces

没有搜索键怎么搜索_电脑快捷截图是哪个键_网页搜索快捷键

Github Codespaces 巧妙避开了这部分冲突的快捷键,用户可以自己到相关配置界面中去设置自己想要的快捷键(但此时用户无法设置成与浏览器冲突的组合)

Theia

网页搜索快捷键_电脑快捷截图是哪个键_没有搜索键怎么搜索

Theia 提供了 Alt + W 作为代替 Cmd + W 的默认组合

Coding

Coding 同上,也是避开冲突 + 自主设置

试用了一圈外部产品后,我枯了。

不出意外的发现友商们在解决此类问题时采用了一种通用的解决方案:避开与浏览器冲突的快捷键组合。这种方式虽然能解决问题,但代价是 需要改变本地用户的使用习惯。

功能上是有了,但体验上产生了差异。

随后到 Theia 老师处取了取经,也没有发现太好的办法:

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

快捷键背后的心智模型

既然业界已经有了通用的 “方案”,为何还要继续纠结这个问题?

从我切身体验来看,我在今年 6 月份来到黄龙,当我将研发活动从本地 VSCode 迁移到 Cloud IDE 时,最难受的是:部分高频快捷键操作与我脑海里的第一反应不一致,典型的如关闭文件 Cmd + W ,即便已经过了 100 多天,我依旧不想用非 Cmd + W 组合来关闭文件。

同时,纠结这一点的用户不止我一个,我们也收到了一些来自用户的反馈,期望能解决高频快捷键的一致性问题。

这是一个很合理的诉求,当我们在 macOS 上下载一个 Native App 时,自然而然的会认为 Cmd + W 是「关闭 XX」, Cmd + N 是「新建 XX」,这是一种约定俗成的 guide line。当然软件开发者们也可以不遵守这个,甚至是反其道行之,那相对应的就会增加用户使用成本,从而戴上真难用的帽子。

快捷键对工具类软件的重要性

在特定的场景下,如果操作鼠标或是操作键盘都能达成某一目的,那么大概率操作键盘会比纯操作鼠标速度来的更快。(当然还有一些场景可能是纯鼠标更快,比如 macOS 用触发角锁屏)

作为工程师,我们可以联想一些日常研发活动时的一些场景,比如跳行、搜索、关闭/新开文件…. 无论是用 Sublime 还是 VSCode ,这些功能都可以用键盘或是鼠标做到,但很多同学会选择使用键盘来唤醒对应的功能。

甚至很多效率工具的核心能力之一是将鼠标操作转用键盘操作来代替,比如 Alfred、Spectacle 等等等等。

同样我们也可以联想一下行业外的其他角色是如何工作的。比如照相馆老板使用 Photoshop,他们键盘上部分按键都快包浆了,可想而知他们有多依赖快捷键的能力,没有快捷键固然还能继续工作,但是效率上会降低很多,而时间就是金钱。

因此可以草率的得出一条结论:遵循心智模型的快捷键组合能提升软件使用的效率。

为什么要用草率这个词,因为这句话是我自己编的 : )

所以我们不但要解决快捷键的 “功能有无” 问题,也要尽可能的解决快捷键 “体验一致” 问题。

所有冲突的快捷键都会失灵吗?

并不是

从现象上看,大体上可以分为两类快捷键:

可以被 preventDefault 的这部分组合很好解,以 Cmd + S 举例

在一个没有额外处理键盘事件的 Web 应用里, Cmd + S 会触发浏览器的 网页保存,见:

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

当通过以下代码取消默认事件后,我们可以愉快的在 listener 中加入 callback

  1. document.addEventListener("keydown", (e) => {

  2. if (e.keyCode === 83 && e.metaKey) {

  3. e.preventDefault();

  4. alert("I AM CMD_S");

  5. }

  6. });

看看效果:

这背后发生的故事就不在本文展开了,相信各位前端老法师们比我更懂 Events

不能被 preventDefault 的快捷键该如何是好

观察上述行为后,可以发现当 CMD + S 未被取消默认事件时,会先执行 cb,随后再是执行默认事件(可以在此同款 demo 中感受一下):

网页搜索快捷键_电脑快捷截图是哪个键_没有搜索键怎么搜索

当 preventDefault 后,自然就变成了:

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

那么,把事件 CMD + S 换为 CMD + W 会发生什么?

将上述 demo 改写后试下:

  1. document.addEventListener("keydown", (e) => {

  2. if (e.keyCode === 69 && e.metaKey) {

  3. e.preventDefault();

  4. alert("I AM CMD_W");

  5. }

  6. });

没有搜索键怎么搜索_电脑快捷截图是哪个键_网页搜索快捷键

直接翻车,说明 CMD + W 类的事件和 CMD + S 类的事件有着本质差别,我们可以在原有的流程图上继续做一个推测,当浏览器对这部分优先级更高的快捷键做出不可逆的副作用响应时,listener 的 cb 即便 preventDefault 也将变得无能为力,因为更高优先级的副作用已经产生了:

那!怎!么!办!啊!

问题不大,办法总归是有的,无非是权衡一下投入和产出。

从流程图上看这个问题的话,思路上大概是在 emit keybords events 与 browser do sth. 之间加点骚东西,一是让不可逆的副作用不再产生,二是让 listener 的 cb 正常执行。只要能满足这两点,这事情基本上就成了,剩下的就看研发成本与实现方式:

有了大致的思路后,继续寻找实现思路的方式,大概分为三个方向:

tupian

我认为工程师的使命是在有限的复杂度中寻找最优解,当梳理完三种思路后,基本就将 Chrome Extension 作为当下的首选实现方案。

一方面 Chrome Extension 能解决用户的几个强诉求组合(如关闭文件 CMD + W ,新建文件 CMD + N ,新开 Tab CMD + T ),解决这几个按键就基本搞定了绝大部分用户。那么还剩下一些 Chrome Extension 也无法拦到的组合,比如 CMD + Q (请不要在此时按这个组合,会错过文末的彩蛋),熟悉 macOS 快捷键的同学应该都知道,这是强退应用的快捷键,理论上它的心智就是强退应用,拦不拦都无所谓了,逃逸就逃逸吧。

另一方面我们的 Cloud IDE 不同于其他中后台产品,我们只需要兼容到 Chrome@latest,为此方案提供了最佳的宿主环境。

基于 Chrome Extension 的快捷键冲突解决方案

综上所述,基本上这个方案的主旋律已经定调了:

具体思路

用一句话概括这个方案的实现思路:在目标应用的 tab 里触发键盘事件时,屏蔽浏览器的原生行为,走 web 应用预期的行为。

如何屏蔽?

从这句话中,我们可以看到,“屏蔽浏览器的原生行为” 是此方案的大前提,那么问题来了,Chrome Extension 可以做到么?

可以

扩展用的很溜的同学应该接触过这个入口,很多扩展是提供快捷键自定义能力的,用于通过某个组合来执行对应的 command,经过实测发现,像 CMD + N CMD + W 等组合可以被扩展快捷键 override,虽然 Chrome 的官方文档并没有提到通过扩展快捷键 override browser 的快捷键是一个 feature,但至少从眼前的行为来看,这条路子是可行的。

没有搜索键怎么搜索_电脑快捷截图是哪个键_网页搜索快捷键

因此最开始的流程图就变成了:

那么这么做会有什么新的问题么?

有的

可以看到这个流程图中缺少了一个环节, browser do sth. 没有了。

有人可能会有疑问:我们确实是想把 browser 响应的事件去掉呀,为什么会有问题呢?

将上图再扩充一下就清晰了:

网页搜索快捷键_没有搜索键怎么搜索_电脑快捷截图是哪个键

从扩充后的流程图中不难发现,实际上我们仍然需要那部分被 override 的 browser 行为,因为用户的浏览器除了跑我们的 Web 应用之外,还需要满足其他的日常浏览需求,如果只是因为想在 Cloud IDE 里通过 CMD + T打开文件 tab,从而失去了 Browser 原生的快速新建页签的能力,那就又绕回了这个问题的起点。

预期是什么?

预期就像图中所描述的,当前 tab 为我们想要 override 的 tab 时,broswer 行为不生效,当前 tab 为其他页面时,继续保留 browser 的行为。

可以完美做到么?

做不到,但是可以假装做得到

先祭出一张 Chrome Extension 的架构图(via kmsfan):

电脑快捷截图是哪个键_网页搜索快捷键_没有搜索键怎么搜索

由图可见,Chrome Extension 有几个核心的概念:

概念不一一展开介绍了,捡两个我们用到的概念说一说。

background js 为开发者提供了丰富的 Chrome API 调用能力,常见的如 开关页面 , 跳转 tab 等基本能力都能通过 Chrome API 模拟出来,因此可以 patch 浏览器的原生事件。

流程图再次变化一下:

没有搜索键怎么搜索_电脑快捷截图是哪个键_网页搜索快捷键

那还有什么问题么?

还有问题

以 Cloud IDE 关闭文件的组合键举例 CMD + OPT + W ,我们虽然通过 Chrome Extension 拦截了 CMD + W ,但是 web 应用的 listener 是注册在 CMD + OPT +W 上的,也就是说我们光拦事件没有用网页搜索快捷键,还需要将对应在 web 应用中的事件补上,而 content js 是 tab 级别并且能获取到对应页面的上下文的,因此可以通过 content js 来 patch 应用注册的事件。

电脑快捷截图是哪个键_网页搜索快捷键_没有搜索键怎么搜索

为什么采用这种方式?

一方面我们的 Web 应用是一个架构很复杂的应用,不希望因这个方案带来侵入性的改造,避免衍生出更多的逻辑分支与环境概念。另一方面,它是一种通用的解决思路,不单单是用来解决 Cloud IDE 遇到的问题,其他的 Web 应用都可以无痕接入类似的方案。

最后看看代码实现,以 CMD + N 新建文件举例:

对于 backgound js 而言,我们需要模拟 新开 window 行为,这个能力 Chrome API 已经提供了,即:

  1. chrome.windows.create()

没有搜索键怎么搜索_电脑快捷截图是哪个键_网页搜索快捷键

对应的 content js,我们需要将 web 应用注册的事件补发,即:

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

将 backgroud 与 content 中的 patch 组合起来,注册到扩展对应的快捷键中:

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

看看最终效果使用方案前

在 Web 应用中使用 CMD + N 打开了新的浏览器窗口,不符合用户的预期

没有搜索键怎么搜索_网页搜索快捷键_电脑快捷截图是哪个键

使用方案后

在 Web 应用中使用 CMD + N 打开了新的 file tab,符合预期

电脑快捷截图是哪个键_没有搜索键怎么搜索_网页搜索快捷键

写到这里,基本上这条野路子就已经描述完了,这依旧不是最完美的解决方案,但至少为解决类似问题提供了新的方向与思路。

啊!终于可以 “关闭” 文件了~

限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688