Webブラウザで操作するサービスには、各操作のキーボードショートカットが割り当てられていることが多いです。
* ショートカットキーを使用する – サポート | Chatwork
* キーボードショートカット - GitHub ヘルプ
* Trello】使用できるショートカットキー(ホットキー) - コガネブログ

どのように実装しているのか調べていたところ、JavaScript のキーボードショートカットライブラリがあることを知ったので、試しに触ってみました。
調べた限りだと、jaywcjlove/hotkeys: ➷ A robust Javascript library for capturing keyboard input. It has no dependencies. と、ccampbell/mousetrap: Simple library for handling keyboard shortcuts in Javascript良い感じに思いましたので、それら2つのライブラリについて記載します。


そもそもキーボードショートカットの意義

最近、情報デザインシリーズ ユーザーエクスペリエンスの測定 - 東京電機大学出版局 科学技術と教育を出版からサポートするいう書籍を読んでいるのですが、その中に、パフォーマンスメトリクスに関する記載があります。
パフォーマンスメトリクスには、以下5つの基本タイプがあります。
1. タスク成功率
タスクが成功したか失敗したかを示す割合。
2. タスク時間
タスク完了にかかる時間。
3. エラー
タスク実行中にユーザーが起こすエラー。
4. 効率
タスクを完了するために、ユーザーが費やす努力の量。
5. 学習可能性
時間とともにパフォーマンスがどう変化するかを計測する。

キーボードショートカットは、タスク時間と、効率に良い影響を与えるように思いました。


キーボードショートカット設定時の留意点

keyboard shortcuts - Are there any guidelines concerning the use of Alt, Ctrl and Shift keys? - User Experience Stack Exchange に、幾つかガイドラインへのリンクがあります。
以下に、幾つかのガイドラインの内容を抜粋して記載します。

Guidelines for Keyboard User Interface Design | Microsoft Docs

Microsoft の ガイドラインです。 Stack Exchange の記事に記載があった内容を日本語訳しました。

  • Assign simple and consistent key combinations.
    単純で一貫性のあるキーの組み合わせを割り当てる。

  • Make shortcut keys customizable.
    ショートカットキーをカスタマイズ可能にする。

  • Use a shortcut with the CTRL key for actions that represent a large-scale effect, such as CTRL+S for save current document.
    現在のドキュメントを保存するためのCTRL + Sなど、大規模な効果を表すアクションには、Ctrlキーを押しながらショートカットを使用する。

  • Use the SHIFT+ key combination for actions that extend or complement the actions of the standard shortcut key. For example, the ALT+TAB shortcut key displays the primary window of a running application. Alternatively, the SHIFT+ALT+TAB key combination allows you to navigate backward through currently running applications that have been previously accessed.
    標準のショートカットキーの動作を拡張または補足する動作には、Shift +キーの組み合わせを使用する。たとえば、Alt + Tabショートカットキーは、実行中のアプリケーションのプライマリウィンドウを表示するが、Shift + Alt + Tabキーを押すと、以前にアクセスした現在実行中のアプリケーションを逆方向に移動することができる。

  • Use the SPACEBAR key as the default action of a control, such as for pressing a button control or toggling the status of a check box control. This is similar to clicking the left or primary mouse button.
    ボタンコントロールを押す、またはチェックボックスコントロールの状態を切り替えるなど、コントロールのデフォルトのアクションとしてSpaceキーを使用する。これは、マウスの左ボタンまたは主ボタンをクリックするのと似ている。

  • Use the ENTER key for the default action of a dialog box, if available.
    可能であれば、ダイアログボックスのデフォルトの動作にはENTERキーを使用する。

  • Use the ESC key to stop or cancel an operation.
    操作を停止またはキャンセルするには、ESCキーを使用する。

  • Avoid modified or case-sensitive letters for shortcuts.
    アクセント記号付き文字や大文字、小文字でショートカットの動作を変更するのは避ける。

  • Avoid using the following characters for shortcut keys: @ {} [] \ ~ | ^ ' < >
    ショートカットキーに次の文字を使用しない。@ {} [] \ ~ | ^ ' < >

  • Avoid ALT+ letter combinations because they may conflict with access keys. In addition, the system uses many specific key combinations for specialized input; for example, ALT+~ invokes an input editor for the Japanese language.
    アクセスキーと競合する可能性があるため、ALT +の文字の組み合わせは避ける。システムは特殊な入力のために多くの特定のキーの組み合わせを使用する。たとえば、ALT +〜は日本語用の入力エディタを呼び出す。

  • Avoid CTRL+ALT combinations because the system interprets this combination in some language versions as an ALTGR key, which generates alphanumeric characters.*
    一部の言語バージョンでは、この組み合わせは英数字を生成するALTGRキーとして解釈されるため、Ctrl + Altの組み合わせは避ける。

  • Avoid assigning combinations that are reserved or defined by the system or are commonly used by other applications.
    システムによって予約または定義されている組み合わせ、または他のアプリケーションで一般的に使用されている組み合わせを割り当てない。

  • Do not use the Windows logo key as a modifier key for non-system-level functions.
    Windowsロゴキーをシステムレベル以外の機能の修飾キーとして使用しない。

Keyboard - User Interaction - macOS - Human Interface Guidelines - Apple Developer

macOS の keyboard の ガイドラインになります。

  • Avoid creating a shortcut by adding a modifier key to an existing shortcut, unless the shortcuts are related.
    ショートカットが関連付けられていない限り、既存のショートカットに修飾キーを追加してショートカットを作成しない。

  • As much as possible, use the Command key as the main modifier key in a keyboard shortcut.
    可能な限り、キーボードショートカットでCommandキーをメイン修飾キーとして使用する。

  • Use the Option key sparingly.
    Optionキーは控えめに使用する。

  • As much as possible, avoid using the Control key.
    できるだけ、Controlキーを使用しない。

  • List multiple modifier keys in the correct order.
    複数の修飾キーを正しい順序で、列挙する。

  • Identify a key with two characters by the lower character, unless Shift is part of the shortcut.
    Shiftがショートカットの一部でない限り、2文字のキーを小文字で識別する。
    Shiftキーがキーボードショートカットの一部である場合は、2文字のうちの上の方の文字を使用してキーを識別する。
    たとえば、ヘルプのキーボードショートカットは、Shift + Command + SlashではなくCommand +疑問符 ? となる。

WAI-ARIA Authoring Practices 1.1

Webコンテンツの<wbr>推奨実装方法集いうドキュメントがあり、5.9 にキーボードショートカットについて記載されています。

  • Making the shortcut easy to learn and remember by using a mnemonic (e.g., Control + S for “Save”) or following a logical or spacial pattern. ニモニック(「保存」の場合はControl + S)を使用するか、論理パターン、または空間パターンを使用することで、ショートカットを習得しやすく覚えやすくする。

  • Localizing the interface, including for differences in which keys are available and how they behave and for language considerations that could impact mnemonics.
    利用可能なキーとその動作の違い、およびニモニックに影響を与える可能性のある言語の考慮事項など、インターフェイスのローカライズをする。

  • Avoiding and managing conflicts with key assignments used by an assistive technology, the browser, or the operating system.
    支援技術、ブラウザ、またはオペレーティングシステムで使用される重要なショートカットキーとの競合を、回避および管理する。

IBM Common User Access - Wikipedia

これは、日本語のWikipediaページがあり、そこにどのような内容か概要が記載されています。
Common User Access - Wikipedia
今の PC の ショートカットやメニューの動作がこのプラクティスに則っているのかと思いました。


個人的な留意点

各ガイドライン、Webサービスから個人的に以下が留意点かなと思いました。

  • 一般的なキーボードショートカットと動作を合わせる。

  • Windowキー等はOSのショートカットが割り当てられているので、使わない。

  • ショートカットに英語的な意味を持たせる。

  • Github の g を押して発動するショートカットは分かりやすく思った。


キーボードショートカットライブラリを試しに使ってみた

hotkeysmousetrapサンプルを動かしてみました。
ほぼ、README.md内容をコピーして作っただけですが、サンプルの補足説明を記載します。

jaywcjlove/hotkeys: ➷ A robust Javascript library for capturing keyboard input. It has no dependencies.

  • サンプル
    Hotkey Example

  • 補足

    • オーソドックスなキー割り当て

      hotkeys('ctrl+a, ctrl+b', function(event,handler) {
          switch(handler.key){
          case "ctrl+a":document.getElementById("left-form").fullName.value = "ctrl+a"; break;
          case "ctrl+b":
              alert('ctrl+b click!');
              break;
          }
      });
      
      キーの組み合わせを、カンマ区切りで指定し、switch文でハンドリングします。
      引数 event は、KeboardEvent で、 handler は、hotkeys の独自オブジェクトです。
      型定義は以下にあります。どんな情報を保持しているのか参考になるかと思います。
      hotkeys/index.d.ts at master · jaywcjlove/hotkeys

    • 全てのキーを割り当て、制御キーの判定
      *全てのキーを割り当てられます。 以下のコードだと、制御キー自体もハンドリングされ、制御キーを押したタイミングでコンソール出力が行われます。

      hotkeys('*', function(e){
          if(hotkeys.shift) console.log('shift is pressed!');
          if(hotkeys.ctrl) console.log('ctrl is pressed!');
          if(hotkeys.alt) console.log('alt is pressed!');
          if(hotkeys.option) console.log('option is pressed!');
          if(hotkeys.control) console.log('control is pressed!');
          if(hotkeys.cmd) console.log('cmd is pressed!');
          if(hotkeys.command) console.log('command is pressed!');
      });
      

    • キーの同時押し制御
      + 記号が同時押しを表します。

      hotkeys('x+s', function(event,handler) {
          if(handler.key === 'x+s') {
              alert('you pressed x+s!');
          }
      });    
      

    • 押したキーの判定
      isPressed押したキーを判定できます。大文字、小文字は同じキーと見なされます。

      hotkeys('a', function(){
          console.log(hotkeys.isPressed("a")); //=> true
          console.log(hotkeys.isPressed("A")); //=> true
          console.log(hotkeys.isPressed(65)); //=> true
      });
      

    • Filterの変更、タグの判定
      hotkeys はデフォルトでINPUT SELECT TEXTAREA 制御対象外にしていて、左記のタグにフォーカスがある場合はハンドリングされません。
      以下のコードでFilterを解除できます。

      hotkeys.filter = function(event){
          return true;
      }
      
      Filterを解除後にm キーを押すと、INPUT タグにフォーカスがある場合は、メッセージが変わります。
      hotkeys('m', function(event,handler){
          if(event.target.tagName === "INPUT"){
          alert('you pressed m!')
          } else {
              alert('you pressed mmmmmmmmmmmmmmmmmmmmmm!');
          }
      });
      
      Filter ですが、一部だけ適用することが難しそうなので、hotkeysのオブジェクトを複製して使うか、Filter を解除して個別にハンドリングする必要があります。

ccampbell/mousetrap: Simple library for handling keyboard shortcuts in Javascript

  • サンプル
    Mousetrap example

  • 補足

    • オーソドックスなキー割り当て
      以下は、ctrl+smeta+s押した際に、alertが表示されます。
      e.preventDefault();e.returnValue = false;デフォルトのブラウザイベントを無効にしています。

      Mousetrap.bind(['ctrl+s', 'meta+s'], function(e) {
          if (e.preventDefault) {
              e.preventDefault();
          } else {
              // internet explorer
              e.returnValue = false;
          }
          alert('ctrl+s meta+s pressed!');
      });
      
      以下は、ctrl+a と、meta+a押した際に、コンソールにログが出力されます。
      return false で、デフォルトブラウザイベントを無効にしています。
      Mousetrap.bind(['ctrl+a','meta+a'], function(e) {
          console.log('ctrl+a meta+a pressed!');
          return false;
      });
      

    • シーケンシャルなキーイベント
      以下は a b c順に押した際に、alert表示されます。
      これで、github 風のキーボードショートカットは作成できそうです。

      Mousetrap.bind('a b c', function(){
          alert('abcがシーケンシャル押下されました。');
      });    
      

    • イベントFilter
      mousetrap には stopCallback というイベントFilter関数があり、デフォルトは以下の実装になります。

      /**
       * should we stop this event before firing off callbacks
       *
       * @param {Event} e
       * @param {Element} element
       * @return {boolean}
       */
      Mousetrap.prototype.stopCallback = function(e, element) {
          var self = this;
      
          // if the element has the class "mousetrap" then no need to stop
          if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
              return false;
          }
      
          if (_belongsTo(element, self.target)) {
              return false;
          }
      
          // Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host,
          // not the initial event target in the shadow tree. Note that not all events cross the
          // shadow boundary.
          // For shadow trees with `mode: 'open'`, the initial event target is the first element in
          // the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event
          // target cannot be obtained.
          if ('composedPath' in e && typeof e.composedPath === 'function') {
              // For open shadow trees, update `element` so that the following check works.
              var initialEventTarget = e.composedPath()[0];
              if (initialEventTarget !== e.target) {
                  element = initialEventTarget;
              }
          }
      
          // stop for input, select, and textarea
          return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
      };    
      
      INPUT、SELECT、TEXTAREAタグ、contenteditable の属性が付与されたタグの場合、基本的にイベントは発動しませんが、class 属性 に mousetrap付与した場合は、例外的に発動するようになっています。 グローバルな Mousetrap の stopCallback を上書き設定すると、全ての処理に影響しますが、以下のようにインスタンス化して使うと、個別にフィルタを上書きすることができ、mキーを押すと、画面上の全ての要素でイベントが発動します。
      var mousetrapEx = new Mousetrap();
      mousetrapEx.stopCallback = function(e, element) {
          return false;
      };
      mousetrapEx.bind(['m'], function(e) {
          alert('m pressed!');
      });
      
      以下のように、class属性にmousetrap追加すると、対象のテキストボックスのみ例外的にイベントが発動します。
      <input id="addressLine1" name="addressLine1" placeholder="Address Line 1" class="form-control mousetrap" required="true" value="" type="text"><
      

使用してみた感想

個人的には、シーケンシャルなキーボードイベントが作れ、個別にイベントフィルタの制御ができる mousetrap が使いやすいと思いました。


参考

以下、参考にした記事になります。

以上です。

コメント

カテゴリー