Tại Cafedev, chúng tôi cung cấp hướng dẫn chi tiết về cách sử dụng các sự kiện bàn phím trong JavaScript từ cơ bản đến nâng cao. Bài viết này sẽ giúp bạn hiểu rõ về sự kiện `keydown` và `keyup`, hai công cụ quan trọng để xử lý các hành động của người dùng qua bàn phím. Bạn sẽ học cách theo dõi các phím nhấn và thả, cũng như cách quản lý các phím tắt và phím đặc biệt, để nâng cao khả năng tương tác trong ứng dụng của mình.

Trước khi tìm hiểu về bàn phím, hãy lưu ý rằng trên các thiết bị hiện đại có nhiều cách khác để “nhập dữ liệu”. Ví dụ, người dùng có thể sử dụng nhận diện giọng nói (đặc biệt trên thiết bị di động) hoặc sao chép/dán bằng chuột. Vì vậy, nếu chúng ta muốn theo dõi bất kỳ dữ liệu đầu vào nào vào trường <input>, thì sự kiện bàn phím không đủ. Có một sự kiện khác là input để theo dõi các thay đổi của trường <input>, bằng bất kỳ phương tiện nào. Đây có thể là sự lựa chọn tốt hơn cho nhiệm vụ đó. Chúng ta sẽ đề cập đến nó trong chương .

Các sự kiện bàn phím nên được sử dụng khi chúng ta muốn xử lý các hành động trên bàn phím (bàn phím ảo cũng tính). Ví dụ, để phản ứng với các phím mũi tên key:Upkey:Down hoặc các phím tắt (bao gồm các tổ hợp phím).

1. Teststand

Để hiểu rõ hơn về các sự kiện bàn phím, bạn có thể sử dụng teststand dưới đây.

Hãy thử các tổ hợp phím khác nhau trong trường văn bản.

https://plnkr.co/edit/svNVUxadu39knD75?p=preview&preview

2. Keydown và keyup

Sự kiện keydown xảy ra khi một phím được nhấn xuống, và sau đó keyup — khi nó được thả ra.

event.code và event.key

Thuộc tính key của đối tượng sự kiện cho phép lấy ký tự, trong khi thuộc tính code của đối tượng sự kiện cho phép lấy “mã phím vật lý”.
Ví dụ, cùng một phím key:Z có thể được nhấn với hoặc không có key:Shift. Điều này tạo ra hai ký tự khác nhau: chữ thường z và chữ hoa Z.

event.key là ký tự chính xác, và nó sẽ khác nhau. Nhưng event.code là như nhau:

Keyevent.keyevent.code
Zz (lowercase)KeyZ
Shift+ZZ (uppercase)KeyZ

Nếu người dùng làm việc với nhiều ngôn ngữ khác nhau, việc chuyển đổi ngôn ngữ có thể tạo ra ký tự hoàn toàn khác thay vì "Z". Ký tự đó sẽ trở thành giá trị của event.key, trong khi event.code luôn giữ nguyên là "KeyZ".
Mỗi phím có một mã code phụ thuộc vào vị trí của nó trên bàn phím. Các mã phím được mô tả trong specification UI Events code.

Ví dụ:

  • Các phím chữ có mã "Key": "KeyA", "KeyB" v.v.
  • Các phím số có mã: "Digit": "Digit0", "Digit1" v.v.
  • Các phím đặc biệt được mã hóa bằng tên của chúng: "Enter", "Backspace", "Tab" v.v.
    Có nhiều kiểu bố trí bàn phím phổ biến, và tài liệu cung cấp mã phím cho từng loại.
    Đọc phần chữ số của tài liệu để biết thêm mã phím, hoặc chỉ cần nhấn phím trong teststand ở trên.

Mặc dù điều này có vẻ hiển nhiên, nhưng người dùng vẫn thường mắc lỗi.

Hãy tránh nhầm lẫn: là KeyZ, không phải keyZ. Kiểm tra như event.code=="keyZ" sẽ không hoạt động: chữ cái đầu tiên của "Key" phải viết hoa.

Còn nếu một phím không tạo ra ký tự nào? Ví dụ, key:Shift hoặc key:F1 hay các phím khác. Đối với những phím đó, event.key gần như giống với event.code:

Keyevent.keyevent.code
F1F1F1
BackspaceBackspaceBackspace
ShiftShiftShiftRight or ShiftLeft

Lưu ý rằng event.code chỉ ra chính xác phím nào đã được nhấn. Ví dụ, hầu hết các bàn phím có hai phím key:Shift: một bên trái và một bên phải. event.code cho chúng ta biết chính xác phím nào đã được nhấn, trong khi event.key đảm nhận “ý nghĩa” của phím: đó là gì (một “Shift”).
Giả sử, chúng ta muốn xử lý một phím tắt: key:Ctrl+Z (hoặc key:Cmd+Z cho Mac). Hầu hết các trình chỉnh sửa văn bản liên kết hành động Hoàn tác với nó. Chúng ta có thể thiết lập một trình lắng nghe trên keydown và kiểm tra phím nào đã được nhấn.

Có một điều dằn vặt ở đây: trong trình lắng nghe như vậy, chúng ta nên kiểm tra giá trị của event.key hay event.code?

Một mặt, giá trị của event.key là một ký tự, nó thay đổi tùy theo ngôn ngữ. Nếu người dùng có nhiều ngôn ngữ trên hệ điều hành và chuyển đổi giữa chúng, cùng một phím có thể cho ra các ký tự khác nhau. Vì vậy, việc kiểm tra event.code có ý nghĩa, vì nó luôn giữ nguyên.
Ví dụ:


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

Mặt khác, có một vấn đề với event.code. Đối với các kiểu bố trí bàn phím khác nhau, cùng một phím có thể có các ký tự khác nhau.
Ví dụ, đây là kiểu bố trí US (“QWERTY”) và kiểu bố trí Đức (“QWERTZ”) dưới đây (từ Wikipedia):


Đối với cùng một phím, kiểu bố trí US có “Z”, trong khi kiểu bố trí Đức có “Y” (các chữ cái bị hoán đổi).
Thực tế, event.code sẽ bằng KeyZ đối với người sử dụng kiểu bố trí Đức khi họ nhấn key:Y.

Nếu chúng ta kiểm tra event.code == 'KeyZ' trong mã của mình, thì đối với người dùng kiểu bố trí Đức, phép kiểm tra này sẽ thành công khi họ nhấn key:Y.

Nghe có vẻ kỳ lạ, nhưng đúng là vậy. specification đề cập rõ ràng đến hành vi như vậy.

Vì vậy, event.code có thể khớp với ký tự sai cho kiểu bố trí không mong muốn. Các ký tự giống nhau trong các kiểu bố trí khác nhau có thể ánh xạ đến các phím vật lý khác nhau, dẫn đến các mã khác nhau. May mắn thay, điều này chỉ xảy ra với một số mã, ví dụ như keyA, keyQ, keyZ (như đã thấy), và không xảy ra với các phím đặc biệt như Shift. Bạn có thể tìm danh sách trong specification.

Để theo dõi ký tự phụ thuộc vào kiểu bố trí một cách đáng tin cậy, event.key có thể là cách tốt hơn.

Mặt khác, event.code có lợi thế là luôn giữ nguyên, gắn với vị trí phím vật lý. Do đó, các phím tắt dựa vào nó hoạt động tốt ngay cả khi chuyển đổi ngôn ngữ.

Chúng ta có muốn xử lý các phím phụ thuộc vào kiểu bố trí không? Trong trường hợp đó, event.key là lựa chọn phù hợp.

Nếu chúng ta muốn một phím tắt hoạt động ngay cả sau khi chuyển đổi ngôn ngữ? Khi đó, event.code có thể là lựa chọn tốt hơn.

3. Tự động lặp lại

Nếu một phím bị nhấn trong thời gian đủ lâu, nó bắt đầu “tự động lặp lại”: keydown được kích hoạt liên tục, và sau đó khi phím được thả ra, chúng ta cuối cùng nhận được keyup. Do đó, việc có nhiều keydown và chỉ một keyup là điều bình thường.
Đối với các sự kiện được kích hoạt bởi tự động lặp lại, đối tượng sự kiện có thuộc tính event.repeat được đặt thành true.

4. Hành động mặc định

Các hành động mặc định thay đổi tùy thuộc vào các hành động có thể được khởi tạo bởi bàn phím.
Ví dụ:

  • Một ký tự xuất hiện trên màn hình (kết quả rõ ràng nhất).
  • Một ký tự bị xóa (key:Delete).
  • Trang được cuộn (key:PageDown).
  • Trình duyệt mở hộp thoại “Lưu trang” (key:Ctrl+S).
  • …và nhiều hành động khác.
    Việc ngăn chặn hành động mặc định trên keydown có thể hủy bỏ hầu hết các hành động này, với ngoại lệ của các phím đặc biệt dựa trên hệ điều hành. Ví dụ, trên Windows, key:Alt+F4 đóng cửa sổ trình duyệt hiện tại. Và không có cách nào để dừng nó bằng cách ngăn chặn hành động mặc định trong JavaScript.
    Ví dụ, <input>dưới đây yêu cầu một số điện thoại, vì vậy nó không chấp nhận các phím ngoài số, +, () hoặc -:

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

Trình xử lý onkeydown ở đây sử dụng checkPhoneKey để kiểm tra phím được nhấn. Nếu nó hợp lệ (từ 0..9 hoặc một trong các ký tự +-()), thì trả về true, nếu không thì trả về false.
Như chúng ta đã biết, giá trị false trả về từ trình xử lý sự kiện, được gán bằng thuộc tính DOM hoặc thuộc tính, như trên, ngăn chặn hành động mặc định, vì vậy không có gì xuất hiện trong <input> cho các phím không vượt qua kiểm tra. (Giá trị true trả về không ảnh hưởng đến bất kỳ điều gì, chỉ có việc trả về false là quan trọng)

Lưu ý rằng các phím đặc biệt, chẳng hạn như key:Backspace, key:Left, key:Right, không hoạt động trong trường hợp nhập liệu. Đây là hiệu ứng phụ của bộ lọc nghiêm ngặt checkPhoneKey, khiến nó trả về false với những phím này.
Chúng ta có thể nới lỏng bộ lọc một chút bằng cách cho phép các phím mũi tên key:Left, key:Right, và các phím key:Delete, key: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">

Giờ đây, các phím mũi tên và xóa hoạt động tốt.
Mặc dù chúng ta có bộ lọc phím, người dùng vẫn có thể nhập bất kỳ giá trị nào bằng cách sử dụng chuột và dán nội dung qua chuột phải. Các thiết bị di động cung cấp các phương pháp khác để nhập giá trị. Do đó, bộ lọc không hoàn toàn đáng tin cậy.

Cách tiếp cận thay thế là theo dõi sự kiện oninput — sự kiện này được kích hoạt sau bất kỳ sửa đổi nào. Tại đây, chúng ta có thể kiểm tra giá trị mới của input.value và sửa đổi hoặc làm nổi bật <input> khi nó không hợp lệ. Hoặc chúng ta có thể sử dụng cả hai trình xử lý sự kiện cùng nhau.

5. Quá khứ trước đó

Trong quá khứ, đã có sự kiện keypress, cũng như các thuộc tính keyCode, charCode, which của đối tượng sự kiện.
Đã có nhiều sự không tương thích giữa các trình duyệt khi làm việc với chúng, khiến các nhà phát triển chuẩn phải loại bỏ tất cả và tạo ra các sự kiện mới, hiện đại (được mô tả trong chương này). Mã cũ vẫn hoạt động, vì các trình duyệt vẫn hỗ trợ chúng, nhưng không còn cần thiết phải sử dụng nữa.

6. Bàn phím di động

Khi sử dụng bàn phím ảo/di động, chính thức được gọi là IME (Input-Method Editor), tiêu chuẩn W3C nêu rõ rằng KeyboardEvent có thể không hoàn toàn chính xác.
Mặc dù một số bàn phím này vẫn có thể sử dụng các giá trị đúng cho e.key, e.code, e.keyCode… khi nhấn các phím như mũi tên hoặc backspace, nhưng không có gì đảm bảo, vì vậy logic bàn phím của bạn có thể không hoạt động đúng trên các thiết bị di động.

7. Tóm tắt

Nhấn phím luôn tạo ra sự kiện bàn phím, bao gồm cả các phím ký tự và phím đặc biệt như key:Shift hoặc key:Ctrl. Ngoại lệ duy nhất là phím key:Fn thường xuất hiện trên bàn phím laptop, vì không có sự kiện bàn phím cho nó do thường được thực hiện ở cấp thấp hơn hệ điều hành.
Các sự kiện bàn phím:

  • keydown – xảy ra khi nhấn phím (sự kiện này sẽ lặp lại tự động nếu phím được giữ lâu).
  • keyup – xảy ra khi thả phím ra.

Các thuộc tính chính của sự kiện bàn phím:

  • code – “mã phím” (“KeyA”, “ArrowLeft”, v.v.), đặc trưng cho vị trí vật lý của phím trên bàn phím.
  • key – ký tự (“A”, “a”, v.v.), đối với các phím không phải ký tự như Esc, thường có giá trị giống như code.

    Trong quá khứ, các sự kiện bàn phím đôi khi được sử dụng để theo dõi đầu vào của người dùng trong các trường hợp nhập liệu. Điều này không đáng tin cậy, vì đầu vào có thể đến từ nhiều nguồn khác nhau. Chúng ta có các sự kiện inputchange để xử lý bất kỳ loại đầu vào nào (sẽ được đề cập sau trong chương ), bao gồm sao chép-dán hoặc nhận diện giọng nói.
    Chúng ta nên sử dụng các sự kiện bàn phím khi thực sự cần xử lý đầu vào từ bàn phím. Ví dụ, để phản ứng với các phím tắt hoặc các phím đặc biệt

Cảm ơn bạn đã theo dõi bài viết từ Cafedev về các sự kiện bàn phím trong JavaScript. Hy vọng bạn đã nắm vững cách sử dụng `keydown` và `keyup` để xử lý các thao tác từ người dùng một cách hiệu quả. Những kiến thức này sẽ giúp bạn tạo ra những ứng dụng tương tác mạnh mẽ hơn. Hãy tiếp tục khám phá các bài học nâng cao khác trên Cafedev để mở rộng kỹ năng JavaScript của bạn!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Javascrypt chi tiết, dễ hiểu từ cơ bản tới nâng cao + Full Bài Tập thực hành nâng cao trình dev

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

  • .

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!