加上快捷鍵,讓你的網站酷起來

Bugtags發表於2015-12-24

偉大的程式設計師都懶。

這話是我從《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 的快捷鍵,不知道你以前有沒有注意過,如果沒有,下次開啟這些網站的時候,在頁面中輸入「?」試試。 enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here

鍵碼與鍵名的對映表

首先你需要定義一張鍵碼與鍵名的對映表:因為我們在文件上監聽鍵盤相關的事件,keyup和keydown,事件觸發時根據我們獲取的事件物件,能讓我們判斷是哪個按鍵的,只有事件物件的which和keyCode屬性,而這都是以鍵碼給出的,並不直觀,尤其是在外掛完成後註冊快捷鍵時,非常不容易記憶和理解;

把你希望構成快捷鍵組合的所有按鍵,全部存進對映表中,可用如下方式給出: enter image description here 在後續的程式中必要的時候,我們都需要把獲取到的鍵碼轉換成鍵名,方便理解。

思路

實際上,我們要解決的最主要的兩個大問題:判斷按鍵組合,觸發組合事件;通俗地說就是:如何獲取使用者按下的快捷鍵(或者組合);使用者按下組合按鍵後如何觸發事件。

判斷按鍵組合

我之前一直說的是按鍵組合,但其實不一定要全部定義多個按鍵按下才能觸發快捷功能,我們完全可以定義某個單個按鍵被按下時就觸發某個行為;總體來說,各個實現了快捷鍵功能的網站,快捷鍵種類有以下三種:

單個按鍵觸發:比如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中刪除即可。 enter image description here

觸發組合事件

接下來就是在handleKeyCombination方法中,處理接收到的使用者當前的按鍵組合,查詢這個組合是否定義了回撥,有就啟用,沒有則忽略。問題是如果這個按鍵組合已經定義了事件,那如何啟用它呢?

要確定啟用方式,就得確定事件的註冊方式;我們需要實現一個事件註冊方法,接受一個快捷鍵組合,以及相應回撥; 有一種很直觀的思路是這樣的:註冊這個組合對應的字串為一個自定義事件,比如m+y,傳入的回撥就是這個事件的回撥,即: enter image description here 然後在使用者按下m以及y之後,傳入這個組合,直接trigger這個事件,自然就會執行相應方法。如果是一個從未定義過回撥的方法,同樣trigger,只不過它沒有繫結事件所以什麼都不做。

但是這樣會有嚴重的效能問題:

註冊一個快捷鍵就得註冊一個自定義事件,j是一個,k是一個,m+y是一個……這樣你註冊的事件會越來越多,對效能是一個比較嚴重的損耗; 假如你想停止使用快捷鍵功能,要麼逐個解綁所有的快捷鍵事件。要麼從源頭上解綁 keyup,keydown相關事件。都是非常麻煩的。 統一的自定義事件

解決方法仍然是用自定義事件,不過全域性我們只註冊一個自定義事件,我們維持一個鍵值對(物件),鍵就是我們註冊的快捷鍵組合,值就是當這個快捷鍵被觸發時執行的方法。註冊新的快捷鍵組合時,往這個物件中新增新的鍵值對即可。 當使用者按鍵時,同樣是將使用者的按鍵組合傳入方法,handleKeyCombination,然後我們檢測使用者按下的按鍵組合作為屬性是否存在於前述物件中,如果存在則觸發一個統一的事件,並傳入這個組合鍵,讓這個統一的事件去分發不同快捷鍵對應的方法進行執行。 enter image description here 這樣做很明顯的好處是

註冊新的事件時只需要操作 definedKeys 物件即可,不再需要再操作事件相關的邏輯,不再新增新的自定義事件。 可以隨時解綁快捷鍵功能,只需要停止觸發自定義的hotkey:active事件就可以了; 事件觸發區域

另外還有一個需要格外注意的點是:我們監聽鍵盤事件是繫結在整個文件物件 document 上的,但是由於瀏覽器的事件傳播機制,如果使用者在與表單互動,比如input,textarea,使用者的文字輸入行為最終會冒泡到 document 上,外掛如果不分情況的進行監聽則很不合理的;我們需要明確區分使用者確實是要在頁面中輸入內容和使用者想觸發快捷鍵這兩種行為,因此在keydown事件中需要檢測事件的target,如果是一個表單互動物件就不要觸發任何事件。

另外一些需要注意的細節

快捷鍵的選取 這個是仁者見仁智者見智,不過總的原則是:照顧使用者習慣,好記;下面這些鍵跟其功能的匹配比較常用: k,j:選擇列表項上一項、下一項,這個來自於 vim 編輯器; ?:顯示快捷鍵視窗,向使用者展示快捷鍵的組合

頁面上下文:同一套快捷鍵在不同的頁面執行不同的功能,大部分快捷鍵都是有上下文,即針對某一個頁面的;如果到了另一個不同的頁面頁面仍然觸發了快捷鍵,執行回撥需要進行容錯處理;

總結

本文簡述了實現一個快捷鍵外掛的思路,並提供了核心的程式碼予以說明;但是這些程式碼片段不足以實現完整地快捷鍵功能,詳細的程式碼可參考 Bugtags 的 gist(https://gist.github.com/sunlianghua/b2467f3c7e739bb169a6),Bugtags 網站中的快捷鍵外掛就是基於這個指令碼。 enter image description here

相關文章