加上快捷键,让你的网站酷起来

伟大的程序员都懒。

这话是我从《PHP 与 MySQL 程序设计》中看来的,来自于 Larry Wall 的一句话:

Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris.

懒的程序员的特征是:能花一步完成的事绝不花两步,即便花一步那一步的时间也是越少越好。所以他们做了很多工具来快捷完成一些繁琐耗时长的任务;放到  Web 上,就有人做了快捷键;这个技术难度并不高,但是把一些非常频繁的操作利用快捷键来触发的话,速度会快不少;毕竟,用鼠标在屏幕上定位一个点然后点击,是比定位键盘按键速度慢的。

下图分别是 Github、Facebook、Twitter、微博、知乎、Gitlab 的快捷键,不知道你以前有没有注意过,如果没有,下次打开这些网站的时候,在页面中输入「?」试试。

键码与键名的映射表

首先你需要定义一张键码与键名的映射表:因为我们在文档上监听键盘相关的事件,keyup和keydown,事件触发时根据我们获取的事件对象,能让我们判断是哪个按键的,只有事件对象的which和keyCode属性,而这都是以键码给出的,并不直观,尤其是在插件完成后注册快捷键时,非常不容易记忆和理解;

把你希望构成快捷键组合的所有按键,全部存进映射表中,可用如下方式给出:


在后续的程序中必要的时候,我们都需要把获取到的键码转换成键名,方便理解。

思路

实际上,我们要解决的最主要的两个大问题:判断按键组合,触发组合事件;通俗地说就是:如何获取用户按下的快捷键(或者组合);用户按下组合按键后如何触发事件。

判断按键组合

我之前一直说的是按键组合,但其实不一定要全部定义多个按键按下才能触发快捷功能,我们完全可以定义某个单个按键被按下时就触发某个行为;总体来说,各个实现了快捷键功能的网站,快捷键种类有以下三种:

单个按键触发:比如j、k;按j选择下一个列表项,k选择上一个列表项(可能灵感来自 VIM 编辑器);

带修饰键的单个按键触发:修饰键指的是shift、control、command、alt等等,通常在一个键盘事件触发时,自动生成的事件对象中,会有专门的属性指明某个修饰键是否被同时按下,其属性值是个布尔值;

多个按键的触发:这里多个按键特指多个非修饰键的按键组合,比如g+m,意味着按下g键之后继续按下m键的组合;

当我们想做一个比较合格的插件时,需要能够处理以上三种情况;以及这三种情况的冲突解决。这里的冲突的指的是:假如我们既定义了a执行某个功能Fa,又定义了b+a执行某个功能Fb,那么当用户按下b键之后继续按下a键,那么程序应当如何响应?是执行Fa还是Fb或者是两者都执行。

我的建议是:在注册快捷键,尽量避免这样的冲突;如果实在无法避免,那这种情况下必须执行Fb,因为如果连用户已经按了多个按键,程序还不触发组合按键事件,那组合快捷键就永远捕获不到了;优先捕获按键组合,其次捕获单个按键。

在 Bugtags 网站上有一个快捷键组合m+y,可以快捷跳转到所有指派给「我」完成的问题;后面的叙述以这个例子来说明。

在快捷键的触发过程中,当某个按键被按下时,我们需要获取它与当前被按下的其他按键所能构成的组合。所以必然需要一个变量pressedKeys(数组)来保存任意时刻被按下的按键,因此我们需要监听keyup和keydown事件;

当keydown时,逻辑稍微复杂点,并且这也是整个快捷键功能的核心;

在某个按键按下时,需要考察当前按键和已经按下的其他按键,看看会构成哪些按键组合(拼接按键组合字符串,作为激活事件的依据):

在keydown事件中只需要专门捕获按键组合,而不用考虑这个按键或者按键组合是否已经定义了执行某个方法。然后把捕获的组合传入另一个方法handleKeyCombination,由他来查找这个按键组合是否定义,以及执行已定义的回调。举个例子:

假如先按下a,没有其他按键在这之前被按下,按键组合就是a,同时a存入pressedKeys,执行handleKeyCombination传入a;

继续按下m,构成按键组合a+m,m存入pressedKeys,执行handleKeyCombination传入a+m;

然后当y被按下时,当前按键跟已经按下的其他按键构成的组合包括a+y和m+y,如果按照严格一点的检测方式,只跟当前按键最近的一次按键匹配,就是m+y,如果你需要定义三个按键的组合快捷键,那当前的按键组合是a+m+y。(不过通常来说,两个按键的组合就已经够用了);

然后仍然要把当前键码存入pressedKeys中;但是有一个特例:那就是修饰键。修饰键最好定义成与非修饰键的组合构成快捷键,在按住一个非修饰键时,我们可以通过查询事件对象来判断某个修饰键是否按下,而不需要通过前述的pressedKeys;因此按下修饰键并不需要保存到pressedKeys里;在 Bugtags 网站中,采用了严格的检测方式,执行handleKeyCombination传入m+y

当keyup时,事情就简单多了,把相应的键码从pressedKeys中删除即可。


触发组合事件

接下来就是在handleKeyCombination方法中,处理接收到的用户当前的按键组合,查询这个组合是否定义了回调,有就激活,没有则忽略。问题是如果这个按键组合已经定义了事件,那如何激活它呢?

要确定激活方式,就得确定事件的注册方式;我们需要实现一个事件注册方法,接受一个快捷键组合,以及相应回调;

有一种很直观的思路是这样的:注册这个组合对应的字符串为一个自定义事件,比如m+y,传入的回调就是这个事件的回调,即:


然后在用户按下m以及y之后,传入这个组合,直接trigger这个事件,自然就会执行相应方法。如果是一个从未定义过回调的方法,同样trigger,只不过它没有绑定事件所以什么都不做。

但是这样会有严重的性能问题:

注册一个快捷键就得注册一个自定义事件,j是一个,k是一个,m+y是一个……这样你注册的事件会越来越多,对性能是一个比较严重的损耗;

假如你想停止使用快捷键功能,要么逐个解绑所有的快捷键事件。要么从源头上解绑keyup,keydown相关事件。都是非常麻烦的。

统一的自定义事件

解决方法仍然是用自定义事件,不过全局我们只注册一个自定义事件,我们维持一个键值对(对象),键就是我们注册的快捷键组合,值就是当这个快捷键被触发时执行的方法。注册新的快捷键组合时,往这个对象中添加新的键值对即可。

当用户按键时,同样是将用户的按键组合传入方法,handleKeyCombination,然后我们检测用户按下的按键组合作为属性是否存在于前述对象中,如果存在则触发一个统一的事件,并传入这个组合键,让这个统一的事件去分发不同快捷键对应的方法进行执行。


这样做很明显的好处是

注册新的事件时只需要操作definedKeys对象即可,不再需要再操作事件相关的逻辑,不再添加新的自定义事件。

可以随时解绑快捷键功能,只需要停止触发自定义的hotkey:active事件就可以了;

事件触发区域

另外还有一个需要格外注意的点是:我们监听键盘事件是绑定在整个文档对象 document 上的,但是由于浏览器的事件传播机制,如果用户在与表单交互,比如input,textarea,用户的文字输入行为最终会冒泡到 document 上,插件如果不分情况的进行监听则很不合理的;我们需要明确区分用户确实是要在页面中输入内容和用户想触发快捷键这两种行为,因此在keydown事件中需要检测事件的target,如果是一个表单交互对象就不要触发任何事件。

另外一些需要注意的细节

快捷键的选取

这个是仁者见仁智者见智,不过总的原则是:照顾用户习惯,好记;下面这些键跟其功能的匹配比较常用:

k,j:选择列表项上一项、下一项,这个来自于 vim 编辑器;

?:显示快捷键窗口,向用户展示快捷键的组合

页面上下文:同一套快捷键在不同的页面执行不同的功能,大部分快捷键都是有上下文,即针对某一个页面的;如果到了另一个不同的页面页面仍然触发了快捷键,执行回调需要进行容错处理;

总结

本文简述了实现一个快捷键插件的思路,并提供了核心的代码予以说明;但是这些代码片段不足以实现完整地快捷键功能,详细的代码可参考 Bugtags 的 gist(https://gist.github.com/sunlianghua/b2467f3c7e739bb169a6),Bugtags 网站中的快捷键插件就是基于这个脚本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 总结: 鼠标事件 1.click与dbclick事件$ele.click()$ele.click(handler(...
    阿r阿r阅读 1,595评论 2 10
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,506评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 这个就是电脑上键盘所有键的功能。 saber_7755 CTRL组合键 Ctrl + A 全选 Ctrl + B ...
    皧烁宝贝阅读 17,301评论 0 33
  • 第一部是讲一个警察,一个传感器专家,一个医生,一个学生和一个建筑师莫名其妙进入到迷宫一样的杀阵中,首先是那个传感器...
    艾尚萱阅读 1,390评论 0 0