Javascript 鍵盤:keydown 和 keyup

2023-02-17 10:54 更新

在我們開始學習鍵盤的相關(guān)內(nèi)容之前,請注意,在現(xiàn)代設(shè)備上,還有其他“輸入內(nèi)容”的方法。例如,人們使用語音識別(尤其是在移動端設(shè)備上)或用鼠標復(fù)制/粘貼。

因此,如果我們想要跟蹤 <input> 字段中的所有輸入,那么鍵盤事件是不夠的。無論如何,還需要一個名為 input 的事件來跟蹤 <input> 字段中的更改。對于這樣的任務(wù)來說,這可能是一個更好的選擇。稍后我們將在 事件:change,input,cut,copy,paste 一章中介紹它們。

當我們想要處理鍵盤行為時,應(yīng)該使用鍵盤事件(虛擬鍵盤也算)。例如,對方向鍵 Up 和 Down 或熱鍵(包括按鍵的組合)作出反應(yīng)。

測試臺

為了更好地理解鍵盤事件,你可以使用下面這個測試臺。

在文本區(qū)域中嘗試使用不同的組合鍵。

示例代碼

Keydown 和 keyup

當一個按鍵被按下時,會觸發(fā) keydown 事件,而當按鍵被釋放時,會觸發(fā) keyup 事件。

event.code 和 event.key

事件對象的 key 屬性允許獲取字符,而事件對象的 code 屬性則允許獲取“物理按鍵代碼”。

例如,同一個按鍵 Z,可以與或不與 Shift 一起按下。我們會得到兩個不同的字符:小寫的 z 和大寫的 Z。

event.key 正是這個字符,并且它將是不同的。但是,event.code 是相同的:

Key event.key event.code
Z z(小寫) KeyZ
Shift+Z Z(大寫) KeyZ

如果用戶使用不同的語言,那么切換到另一種語言將產(chǎn)生完全不同的字符,而不是 "Z"。它將成為 event.key 的值,而 event.code 則始終都是一樣的:"KeyZ"。

“KeyZ” 和其他按鍵代碼

每個按鍵的代碼都取決于該按鍵在鍵盤上的位置。UI 事件代碼規(guī)范 中描述了按鍵代碼。

例如:

  • 字符鍵的代碼為 ?"Key<letter>"?:?"KeyA"?,?"KeyB"? 等。
  • 數(shù)字鍵的代碼為:?"Digit<number>"?:?"Digit0"?,?"Digit1"? 等。
  • 特殊按鍵的代碼為按鍵的名字:?"Enter"?,?"Backspace"?,?"Tab"? 等。

有幾種廣泛應(yīng)用的鍵盤布局,該規(guī)范給出了每種布局的按鍵代碼。

有關(guān)更多按鍵代碼,請參見 規(guī)范的字母數(shù)字部分,或者只需在上面的 測試臺 中按下一個按鍵。

大小寫敏感:?"KeyZ"?,不是 ?"keyZ"?

這是顯而易見的,但人們?nèi)詴沐e。

請規(guī)避錯誤類型:它是 KeyZ,而不是 keyZ。像 event.code=="keyZ" 這樣的檢查不起作用:"Key" 的首字母必須大寫。

如果按鍵沒有給出任何字符呢?例如,Shift 或 F1 或其他。對于這些按鍵,它們的 event.key 與 event.code 大致相同:

Key event.key event.code
F1 F1 F1
Backspace Backspace Backspace
Shift Shift ShiftRight 或 ShiftLeft

請注意,event.code 準確地標明了哪個鍵被按下了。例如,大多數(shù)鍵盤有兩個 Shift 鍵,一個在左邊,一個在右邊。event.code 會準確地告訴我們按下了哪個鍵,而 event.key 對按鍵的“含義”負責:它是什么(一個 “Shift”)。

假設(shè),我們要處理一個熱鍵:Ctrl+Z(或 Mac 上的 Cmd+Z)。大多數(shù)文本編輯器將“撤銷”行為掛在其上。我們可以在 keydown 上設(shè)置一個監(jiān)聽器,并檢查哪個鍵被按下了。

這里有個難題:在這樣的監(jiān)聽器中,我們應(yīng)該檢查 event.key 的值還是 event.code 的值?

一方面,event.key 的值是一個字符,它隨語言而改變。如果訪問者在 OS 中使用多種語言,并在它們之間進行切換,那么相同的按鍵將給出不同的字符。因此檢查 event.code 會更好,因為它總是相同的。

像這樣:

document.addEventListener('keydown', function(event) {
  if (event.code == 'KeyZ' && (event.ctrlKey || event.metaKey)) {
    alert('Undo!')
  }
});

另一方面,event.code 有一個問題。對于不同的鍵盤布局,相同的按鍵可能會具有不同的字符。

例如,下面是美式布局(“QWERTY”)和德式布局(“QWERTZ”)—— 來自 Wikipedia:


對于同一個按鍵,美式布局為 “Z”,而德式布局為 “Y”(字母被替換了)。

從字面上看,對于使用德式布局鍵盤的人來說,但他們按下 Y 時,event.code 將等于 KeyZ。

如果我們在代碼中檢查 event.code == 'KeyZ',那么對于使用德式布局鍵盤的人來說,當他們按下 Y 時,這個測試就通過了。

聽起來確實很怪,但事實確實如此。規(guī)范 中明確提到了這種行為。

因此,event.code 可能由于意外的鍵盤布局而與錯誤的字符進行了匹配。不同鍵盤布局中的相同字母可能會映射到不同的物理鍵,從而導(dǎo)致了它們有不同的代碼。幸運的是,這種情況只發(fā)生在幾個代碼上,例如 keyA,keyQ,keyZ(我們已經(jīng)看到了),而對于諸如 Shift 這樣的特殊按鍵沒有發(fā)生這種情況。你可以在 規(guī)范 中找到該列表。

為了可靠地跟蹤與受鍵盤布局影響的字符,使用 event.key 可能是一個更好的方式。

另一方面,event.code 的好處是,綁定到物理鍵位置的 event.code 會始終保持不變。因此,即使在切換了語言的情況下,依賴于它的熱鍵也能正常工作。

我們想要處理與布局有關(guān)的按鍵?那么 event.key 是我們必選的方式。

或者我們希望一個熱鍵即使在切換了語言后,仍能正常使用?那么 event.code 可能會更好。

自動重復(fù)

如果按下一個鍵足夠長的時間,它就會開始“自動重復(fù)”:keydown 會被一次又一次地觸發(fā),然后當按鍵被釋放時,我們最終會得到 keyup。因此,有很多 keydown 卻只有一個 keyup 是很正常的。

對于由自動重復(fù)觸發(fā)的事件,event 對象的 event.repeat 屬性被設(shè)置為 true。

默認行為

默認行為各不相同,因為鍵盤可能會觸發(fā)很多可能的東西。

例如:

  • 出現(xiàn)在屏幕上的一個字符(最明顯的結(jié)果)。
  • 一個字符被刪除(?Delete? 鍵)。
  • 滾動頁面(?PageDown? 鍵)。
  • 瀏覽器打開“保存頁面”對話框(?Ctrl+S?)
  • ……等。

阻止對 keydown 的默認行為可以取消大多數(shù)的行為,但基于 OS 的特殊按鍵除外。例如,在 Windows 中,Alt+F4 會關(guān)閉當前瀏覽器窗口。并且無法通過在 JavaScript 中阻止默認行為來阻止它。

例如,下面的這個 <input> 期望輸入的內(nèi)容為一個電話號碼,因此它不會接受除數(shù)字,+() 和 - 以外的按鍵:

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') || ['+','(',')','-'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="請輸入手機號" type="tel">

這里 onkeydown 的處理程序使用 checkPhoneKey 來檢查被按下的按鍵。如果它是有效的(0..9 或 +-() 之一),那么將返回 true,否則返回 false。

我們都知道,像上面那樣,從事件處理程序返回 false 會阻止事件的默認行為,所以如果按下的按鍵未通過按鍵檢查,那么 <input> 中什么都不會出現(xiàn)(從事件處理程序返回 true 不會對任何行為產(chǎn)生影響,只有返回 false 會產(chǎn)生對應(yīng)的影響)。

請注意,像 Backspace、LeftRight 這樣的特殊按鍵在 <input> 中無效。這是嚴格過濾器 checkPhoneKey 的副作用。這些按鍵會使 checkPhoneKey 返回 false

讓我們將過濾條件放寬一點,允許 Left、Right、Delete 和 Backspace 按鍵:

<script>
function checkPhoneKey(key) {
  return (key >= '0' && key <= '9') ||
    ['+','(',')','-','ArrowLeft','ArrowRight','Delete','Backspace'].includes(key);
}
</script>
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel">

現(xiàn)在方向鍵和刪除鍵都能正常使用了。

……即使我們對按鍵進行了過濾,但仍然可以使用鼠標右鍵單擊 + 粘貼來輸入任何內(nèi)容。移動端設(shè)備提供了其他輸入內(nèi)容的方式。因此,這個過濾器并不是 100% 可靠。

另一種方式是跟蹤 oninput 事件 —— 在任何修改后都會觸發(fā)此事件。這樣我們就可以檢查新的 input.value,并在其無效時修改它/高亮顯示 <input>?;蛘呶覀兛梢酝瑫r使用這兩個事件處理程序。

歷史遺留

過去有一個 keypress 事件,還有事件對象的 keyCode、charCode 和 which 屬性。

大多數(shù)瀏覽器對它們都存在兼容性問題,以致使該規(guī)范的開發(fā)者不得不棄用它們并創(chuàng)建新的現(xiàn)代的事件(本文上面所講的這些事件),除此之外別無選擇。舊的代碼仍然有效,因為瀏覽器還在支持它們,但現(xiàn)在完全沒必要再使用它們。

移動端鍵盤

當使用虛擬/移動端鍵盤時,更正式一點的名字叫做 IME(Input-Method Editor),W3C 標準規(guī)定 KeyboardEvent 的 e.keyCode 應(yīng)該為 229,并且 e.key 應(yīng)該為 "Unidentified"

當按下某些按鍵(例如箭頭或退格鍵)時,雖然其中一些鍵盤可能仍然使用正確的值來表示 e.key、e.code、e.keyCode…,但并不能保證所有情況下都能對應(yīng)正確的值。所以你的鍵盤邏輯可能并不能保證適用于移動設(shè)備。

總結(jié)

按一個按鍵總是會產(chǎn)生一個鍵盤事件,無論是符號鍵,還是例如 Shift 或 Ctrl 等特殊按鍵。唯一的例外是有時會出現(xiàn)在筆記本電腦的鍵盤上的 Fn 鍵。它沒有鍵盤事件,因為它通常是被在比 OS 更低的級別上實現(xiàn)的。

鍵盤事件:

  • ?keydown? —— 在按下鍵時(如果長按按鍵,則將自動重復(fù)),
  • ?keyup? —— 釋放按鍵時。

鍵盤事件的主要屬性:

  • ?code? —— “按鍵代碼”(?"KeyA"?,?"ArrowLeft"? 等),特定于鍵盤上按鍵的物理位置。
  • ?key? —— 字符(?"A"?,?"a"? 等),對于非字符(non-character)的按鍵,通常具有與 ?code? 相同的值。

過去,鍵盤事件有時會被用于跟蹤表單字段中的用戶輸入。這并不可靠,因為輸入可能來自各種來源。我們有 input 和 change 事件來處理任何輸入(稍后我們會在 事件:change,input,cut,copy,paste 一章中進行介紹)。它們在任何類型的輸入(包括復(fù)制粘貼或語音識別)后觸發(fā)。

當我們真的想要鍵盤時,我們應(yīng)該使用鍵盤事件。例如,對熱鍵或特殊鍵作出反應(yīng)。

任務(wù)


擴展熱鍵

重要程度: 5

創(chuàng)建一個 runOnKeys(func, code1, code2, ... code_n) 函數(shù),在同時按下 code1, code2, ... code_n 鍵時運行函數(shù) func。

例如,當按鍵 "Q" 和 "W" 被一起按下時(任何語言中,無論是否 CapsLock),下面的代碼將顯示 alert

runOnKeys(
  () => alert("Hello!"),
  "KeyQ",
  "KeyW"
);

在新窗口中演示


解決方案

我們應(yīng)該使用兩個處理程序:document.onkeydown 和 document.onkeyup。

讓我們創(chuàng)建一個集合 pressed = new Set() 來保存當前被按下的鍵。

第一個處理程序把當前被按下的鍵添加到集合中,而第二個處理程序?qū)⒈凰砷_的按鍵從集合中移除。我們每次在 keydown 上檢查我們是否按下了足夠多的鍵,如果是,則運行函數(shù) func。

使用沙箱打開解決方案。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號