웹 앱에서 키보드 단축키 구현(Vanilla JS 및 React 샘플 포함)

안녕하세요. YAMAP에서 프런트 엔드 엔지니어를 하고 있는 @SotaSuzuki 이라고 합니다.
이 기사는 YAMAP 엔지니어 Advent Calendar 2019의 10일째 기사입니다.
연투로 죄송하지만, 아무쪼록 잘 부탁드립니다.

소개



현재 실시하고 있는 사내 어플리케이션의 개발에 있어서, 좋은 느낌의 인터페이스의 키보드 단축키를 구현하고 싶다고 생각해 시행착오했습니다. 그 결과, 비교적 이미지에 가까운 것을 할 수 있었으므로 공유합니다.

사용할 라이브러리



스크래치로 개발하는 것은 windows 의 ctrlKey 와 mac 의 metaKey 의 취급 등, 비교적 번거로울 것 같았으므로, 이하의 라이브러리를 사용하고 있습니다.

  • HotKeys.js
  • 키보드 단축키를 쉽게 구현할 수있는 다기능 라이브러리
  • JavaScript에서 키보드 단축키를 구현한다면 이것이 사실 표준이라고 할 수 있습니다.


  • 키코드
  • 키보드 이벤트 event.keyCode키맵 에 따라 문자열로 변환


  • 이번에 만드는 것



    아래와 같은 인터페이스의 키보드 단축키를 구현합니다.
    interface KeyMap {
      sequence: string; // 'ctrl+a', 'ctrl+shift+a', ...
      handler(evt: Event): void;
    }
    
    type KeyMaps = KeyMap[];
    

    HotKeys.js도 상당히 다기능이지만 그대로 위의 인터페이스의 키보드 단축키를 구현하려면 조금 번거로운 일을 해야 한다고 했습니다. (HotKeys.js 단독으로의 구현은 후술. 의외로 번거롭지 않았다 )

    Vanilla JavaScript (Babel)로 구현



    hotkeys.js
    import hotkeys from 'hotkeys-js';
    import keycode from 'keycode';
    import { undo, redo } from './funcs'; // undo, redo 関数は既に実装されているものを import
    
    const orderedModifierKeys = Object.freeze(['command', 'ctrl', 'shift', 'alt']);
    
    const getKeySequence = (codes, options = {
      splitKey = '+',
    }) => {
      const keys = codes
        .map(code => keycode(code)) // keycode() で keyCode に対応した文字列へ変換
        .map(code => (code.endsWith('command') ? 'command' : code)); // command キーは 'left command', 'right command' として取得されるため、'command' に統一
    
      const modifierKeys = keys
        .filter(orderedModifierKeys.includes)
        .sort((a, b) => { // modifierKeys を orderedModifierKeys の順番に並べ替える
          const aIndex = orderedModifierKeys.findIndex(key => key === a);
          const bIndex = orderedModifierKeys.findIndex(key => key === b);
          return aIndex - bIndex;
        });
    
      const otherKeys = keys.filter(key => !orderedModifierKeys.includes(key));
    
      return modifierKeys.concat(otherKeys).join(splitKey);
    };
    
    const keyMaps = [
      {
        sequence: 'command+z',
        handler:() => undo(),
      },
      {
        sequence: 'command+shift+z',
        handler: () => redo(),
      },
      // ... other cases
    ];
    
    const registerHotkeys = () => {
      hotkeys('*', evt => {
        const keyCodes = hotkeys.getPressedKeyCodes();
        const sequence = getKeySequence(keyCodes);
        const keyMap = keyMaps.find(keyMap => keyMap.sequence === sequence);
        if (!keyMap) return; // ショートカットが存在しない場合は何もしない
        keyMap.handler(evt);
      })
    }
    
    const unregisterrHotkeys = () => {
      hotkeys.unbind('*');
    }
    
    window.onload = () => {
      registerHotkeys();
    }
    
    window.onbeforeunload = () => {
      unregisterrHotkeys();
    }
    

    React에서 구현



    React에서는 useHotkeys라는 Custom Hook을 구현해 보겠습니다.

    useHotkeys.ts
    import { useEffect } from 'react';
    import hotkeys from 'hotkeys-js';
    import keycode from 'keycode';
    
    // --- 中略 ---
    
    export const useHotkeys = (): void => {
      const keyMaps = [
        // ...
      ];
    
      useEffect(() => {
        hotkeys('*', (evt: Event) => {
          const keyCodes = hotkeys.getPressedKeyCodes();
          const sequence = getKeySequence(keyCodes);
          const keyMap = keyMaps.find(keyMap => keyMap.sequence === sequence);
          if (!keyMap) return;
          keyMap.handler(evt);
        });
    
        return () => {
          hotkeys.unbind('*');
        };
      }, [keyMaps]
    };
    

    이상



    이상입니다.

    사족이지만 어려운 일을 하지 않고 키보드 단축키를 구현한다면 HotKeys.js 에서 아래와 같은 구현을 하는 것이 빠를지도 모릅니다.
    import hotkeys from 'hotkeys-js';
    
    const keyMaps = [
      {
        sequence: 'ctrl+a',
        handler: handleKeypressCtrlA,
      },
    ];
    
    const sequences = keyMaps.map(keyMap => keyMap.sequence)
    
    hotkeys(sequences.join(','), (evt, handler) => {
      const keyMap = keyMaps.find(({ sequence }) => sequence === handler.key);
      if (!keyMap) return;
      keyMap.handler(evt);
    });
    

    좋은 웹페이지 즐겨찾기