Alternative for event's deprecated KeyboardEvent.which property

后端 未结 3 593
抹茶落季
抹茶落季 2021-02-04 03:25

MDN states that KeyboardEvent.which is deprecated. How can I substitute it for a non-deprecated version?

For example, I have the following:

相关标签:
3条回答
  • 2021-02-04 03:47

    From the specification:

    which of type unsigned long, readonly

    which holds a system- and implementation-dependent numerical code signifying the unmodified identifier associated with the key pressed. In most cases, the value is identical to keyCode

    keyCode of type unsigned long, readonly

    keyCode holds a system- and implementation-dependent numerical code signifying the unmodified identifier associated with the key pressed. Unlike the KeyboardEvent.key attribute, the set of possible values are not normatively defined in this specification. Typically, these value of the keyCode should represent the decimal codepoint in ASCII [RFC20][US-ASCII] or Windows 1252 [WIN1252], but may be drawn from a different appropriate character set. Implementations that are unable to identify a key use the key value '0'.

    See Legacy key models for more details on how to determine the values for keyCode.

    (I have omitted the links)

    So it is quite easy to create a version that is compatible with the specification. The easiest version just returns 0 for each key.

    A slightly more involved version takes event.key and grabs it's ASCII number. You can do the same for the control keys (altKey, ctrlKey, shiftKey).

    In short, as it stands the behavior of which is different between systems and browsers. Using the non-deprecated event information you can create a more robust version that removes these differences and will be more future proof.

    You can check the behavior of your version with the implementation of which in major browsers. If you are not using any edge cases your version will be both simple and compatible.

    0 讨论(0)
  • 2021-02-04 03:55

    As the other answers pointed out, event.which has one main problem: it does not return the same number for different browsers or computers (maybe this is why it is deprecated). Therefore, there is no perfect substitute for it, since it will output different numbers for different users.

    So the main problem in trying to create a substitute for it (let's name it: function whichSubstitute(event)) is that the Meta and Shift keys, for example, don't have a unique number that whichSubstitute should get when one of them is pressed, it varies according to OS.

    With that in mind, there are two approaches for getting the unicode code point for the user's input.

    1. Getting the unicode value for the character that the user inputted (e.g., ü, which would be 'ü'.codePointAt(0)).
    2. Getting a numeric value for the character that corresponds to the physical key pressed in the keyboard, which might be different from what was inputted to the text field. As AnilRedShift mentioned, the keyboard layout might change the "natural" output from that key in the keyboard, in such a way that the key s might output o. In this case, we'd get 's'.codePointAt(0), instead of getting the value for 'o' (that is, what was actually outputted), like we would get using the first approach. More on this from MDN:

    For example, the code returned is "KeyQ" is for the "q" key on a QWERTY layout keyboard, but the same code value also represents the "'" key on Dvorak keyboards and the "a" key on AZERTY keyboards. That makes it impossible to use the value of code to determine name of the key is to users if they're not using an anticipated keyboard layout.

    In short: approach number 1 gets the unicode code point for ü, whereas approach number 2 gets the code points for SHIFT, 6 and U (since SHIFT+6+U == ü).

    In this answer, we'll use String.prototype.codePointAt() instead of String.prototype.charCodeAt(). The differences are well explained here. The reason is that we can get the whole unicode number with .codePointAt(0), whereas the .charCodeAt(0) would lack .codePointAt(1) to complete the UTF-16 encoded code point.

    For approach number 1, we can use the following code:

    function whichSubstitute(event) {
      const theKey = event.key;
      if (theKey.length === 1) {
        return theKey.codePointAt(0);
      }
      switch (theKey) {
        case "Backspace":
          return 8;
        case "Tab":
          return 9;
        case "Enter":
          return 13;
        case "Alt":
          return 18;
        case "Escape":
          return 27;
        case "Delete":
          return 127;
    
        case "Dead": //As of july 2018, Firefox has no support for "Dead" keys https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
          {}
          break;
        case "Unidentified":
          alert("handle the 'Unidentified' if you want to!");
      }
    
      /*** there are many other possible key values https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
            but, AFAIK, there are no unicode code points for them, such as:
    
      switch (theKey) {
        case "AltGraph":
        case "CapsLock":
        case "Control":
        case "Fn":
        case "FnLock":
        ...
    
           event.which may output some number for them, but it is not consistent across
           browsers/machines and they may overlap with other code points. For example:
    
      case "ArrowUp":
        return 38; //this overlaps with "&"
      case "ArrowLeft":
        return 37; //this overlaps with "%"
      case "ArrowDown":
        return 40; //this overlaps with "("
      case "ArrowRight":
        return 39; //this overlaps with "'"
    
      ***/
    
      return 0;
    }
    
    //test
    document.onkeydown = (event) => {
      console.log('whichSubstitute: ' + whichSubstitute(event) + '; event.which: ' + event.which);
      //note that whichSubstitute gets the ASCII number of 'a', while event.which only gets the ASCII number of 'A' (upper case, always)
    }

    This, of course, does not solve the problem of getting only one unique consistent number for a pressed key when there is no unicode code point for it (as in the case of Meta). Such keys need to be handled by the programmer according to her/his needs.

    For approach number 2, we can use the following code:

    function whichSubstitute(event) {
      const theChar = event.code;
      if (theChar.startsWith('Key')) {
        return theChar.codePointAt(3);
      }
      if (theChar.startsWith('Digit')) {
        return theChar.codePointAt(5);
      }
    
      switch (theChar) {
        case "Backspace":
          return 8;
        case "Tab":
          return 9;
        case "Enter":
          return 13;
        case "Alt":
          return 18;
        case "Escape":
          return 27;
        case "Delete":
          return 127;
        case "Minus":
          return 45;
        case "Plus":
          return 43;
        case "Equal":
          return 61;
        case "Delete":
          return 127;
        case "BracketRight":
          return 93;
        case "BracketLeft":
          return 91;
        case "Backslash":
          return 92;
        case "Slash":
          return 47;
        case "Semicolon":
          return 59;
        case "Colon":
          return 58;
        case "Comma":
          return 44;
        case "Period":
          return 46;
        case "Space":
          return 32;
        case "Quote":
          return 34;
        case "Backquote":
          return 39;
    
        //there are also "Numpad....." variants
    
        case "Unidentified":
          alert("handle the 'Unidentified' if you want to!");
      }
    
      /*** there are many other possible character values https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
            but, AFAIK, there are no unicode code points for them, such as:
    
      switch (theKey) {
        case "AltLeft":
        case "CapsLock":
        case "ControlRight":
        case "Fn":
        case "NumpadDecimal":
        ...
    
           event.which may output some number for them, but it is not consistent across
           browsers/machines and they may overlap with other code points.
    
      ***/
    
      return 0;
    }
    
    //test
    document.onkeydown = (event) => {
      console.log('whichSubstitute: ' + whichSubstitute(event) + '; event.which: ' + event.which);
    }

    This second approach might be useless, since the same physical key might output different unicode characters according to different keyboard layouts. The users might have no idea of which key they should press.

    Related: https://www.w3.org/TR/uievents/#keys

    0 讨论(0)
  • 2021-02-04 04:05

    TL;DR: These are the rules you should follow:

    • When getting text input from the user, use the keypress event along with e.key
    • For shortcuts and other combinations, the built-in way is to use keydown/keyup and check the various modifier keys. If you need to detect chords, you may need to build a state machine.

    Background

    Keyboard input is split into two phases - keydown/keyup pairs, which track physical keys being pressed, and composed characters that combines multiple sequences of keys to compute a character.

    Getting "text"

    If you want to know what the operating system thinks the composed sequence is, you should use KeyboardEvent.key

    Sample code:

    document.getElementById('a').addEventListener('keypress', e => console.log(e.key));
    <input id="a" type="text" placeholder="type here">

    The reason you want to do this most of the time is because many languages compose characters with several keypresses. The easiest for US-101 keyboards to understand is pressing the shift key + a is A, compared to just pressing a. For languages like Russian, with the altgr dead key, this becomes especially important.

    The point I am trying to make is that doing all of this work yourself - detecting key sequences and determining the correct text output is a hard problem™. It is the job of the operating system for a reason.

    Now, for older browsers, you may not want to use e.key for lack of older support. Then you can fall back to things like which, or other non-standard approaches.

    At some point in the future, keypress may be removed by browsers. The beforeinput event is supposed to be the replacement. However, that event is only supported in chrome, so I'm omitting in here for brevity's sake.

    Getting keystrokes

    Now then, suppose you are not tracking text, but rather key sequences. This is for things like games, or listening to ctrl-c etc. In this case, the correct thing to do is to listen to keydown/keyup events. For modifier keys, you can simply listen to the ctrlKey, shiftKey, and metaKey properties of the event. See below:

    document.getElementById('a').addEventListener('keydown', (e) => {
      const states = {
        alt: e.altKey,
        ctrl: e.ctrlKey,
        meta: e.metaKey,
        shift: e.shiftKey,
      };
      const key = e.key;
      const code = e.code;
      console.log(`keydown key: ${key}, code: ${code}`, states);
    });
    <input id="a" type="text" placeholder="press ctrl">

    As an example, when pressing shift-o on my keyboard, I get the following:

    keydown key: Shift, code: ShiftLeft {
      "alt": false,
      "ctrl": false,
      "meta": false,
      "shift": true
    }
    keydown key: O, code: KeyS {
      "alt": false,
      "ctrl": false,
      "meta": false,
      "shift": true
    }
    

    Hopefully the states part is pretty self-evident. They say whether that modifier key was pressed while the other key is down.

    The difference between key and code has to do with keyboard layouts. I am using the software dvorak layout. Thus when I type the s key, the scan code that goes to the operating system says s, but then the OS converts that to o because it's dvorak. Code in this case always refers to the scan code (physical key being pressed), while the key corresponds to the operating system's best-effort to figure out what the "text" will be. This isn't always possible, especially with other languages. Again, this is why using the key for the keypress is the right way to go about it.

    3rd party libraries

    If this doesn't sound particularly easy, that's because it's not. The last time I was looking at this, I came across the mousetrap library, although I'm not sure I would recommend it, given some of the issues I found. It does, however, show an example of building a state machine to track key chords.

    Addendum

    This is also why you need to track keydown/keyup if you want to eat keystrokes. Since there is no "text" for ctrl+c, you won't get a proper keypress, and thus the browser will natively handle it. If you want to run your own behavior, you need to e.preventDefault() on the keydown itself. (Some of the followup events like copy can also be cancelled, but that's not universally true)

    If you also just need to track keys inserted after-the-fact into an input field (or contenteditable div), see the input event.

    History: Updated 8/2019 to change keypress->beforeinput

    0 讨论(0)
提交回复
热议问题