Lưu ý: keyCode hiện không được dùng nữa.
Phát hiện nhiều tổ hợp phím là dễ dàng nếu bạn hiểu khái niệm
Cách tôi làm là như thế này:
var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
e = e || event; // to deal with IE
map[e.keyCode] = e.type == 'keydown';
/* insert conditional here */
}
Mã này rất đơn giản: Vì máy tính chỉ truyền một lần nhấn phím cùng một lúc, một mảng được tạo để theo dõi nhiều phím. Mảng sau đó có thể được sử dụng để kiểm tra một hoặc nhiều khóa cùng một lúc.
Để giải thích, giả sử bạn nhấn Avà B, mỗi lần kích hoạt một keydown
sự kiện đặt map[e.keyCode]
giá trị của e.type == keydown
, đánh giá là đúng hoặc sai . Bây giờ cả hai map[65]
và map[66]
được thiết lập để true
. Khi bạn buông tay A
, keyup
sự kiện sẽ kích hoạt, khiến logic tương tự xác định kết quả ngược lại cho map[65]
(A), hiện là sai , nhưng vì map[66]
(B) vẫn "xuống" (nó không kích hoạt sự kiện keyup), nó vẫn đúng .
Các map
mảng, thông qua cả hai sự kiện, trông như thế này:
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Có hai điều bạn có thể làm bây giờ:
A) Trình ghi nhật ký khóa ( ví dụ ) có thể được tạo làm tài liệu tham khảo cho lần sau khi bạn muốn nhanh chóng tìm ra một hoặc nhiều mã khóa. Giả sử bạn đã xác định một phần tử html và chỉ nó với biến element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Lưu ý: Bạn có thể dễ dàng lấy một phần tử theo id
thuộc tính của nó .
<div id="element"></div>
Điều này tạo ra một yếu tố html có thể dễ dàng được tham chiếu trong javascript với element
alert(element); // [Object HTMLDivElement]
Bạn thậm chí không phải sử dụng document.getElementById()
hoặc $()
để lấy nó. Nhưng vì mục đích tương thích, hãy sử dụng jQuery$()
được sử dụng rộng rãi hơn.
Chỉ cần đảm bảo thẻ script xuất hiện sau phần thân của HTML. Mẹo tối ưu hóa : Hầu hết các trang web tên tuổi đặt thẻ script sau thẻ body để tối ưu hóa. Điều này là do thẻ script chặn các phần tử tiếp theo tải cho đến khi tập lệnh của nó được tải xuống xong. Đặt nó trước nội dung cho phép nội dung tải trước.
B (đó là nơi bạn quan tâm) Bạn có thể kiểm tra một hoặc nhiều khóa tại thời điểm đó /*insert conditional here*/
, lấy ví dụ này:
if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
alert('Control Shift C');
}
Chỉnh sửa : Đó không phải là đoạn dễ đọc nhất. Khả năng đọc là quan trọng, vì vậy bạn có thể thử một cái gì đó như thế này để dễ nhìn hơn:
function test_key(selkey){
var alias = {
"ctrl": 17,
"shift": 16,
"A": 65,
/* ... */
};
return key[selkey] || key[alias[selkey]];
}
function test_keys(){
var keylist = arguments;
for(var i = 0; i < keylist.length; i++)
if(!test_key(keylist[i]))
return false;
return true;
}
Sử dụng:
test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')
Tốt hơn chưa?
if(test_keys('ctrl', 'shift')){
if(test_key('A')){
alert('Control Shift A');
} else if(test_key('B')){
alert('Control Shift B');
} else if(test_key('C')){
alert('Control Shift C');
}
}
(kết thúc chỉnh sửa)
Ví dụ này kiểm tra cho CtrlShiftA, CtrlShiftBvàCtrlShiftC
Nó chỉ đơn giản như vậy :)
Ghi chú
Theo dõi mã khóa
Theo nguyên tắc chung, nên sử dụng mã tài liệu, đặc biệt là những thứ như Mã khóa (như // CTRL+ENTER
) để bạn có thể nhớ chúng là gì.
Bạn cũng nên đặt các mã khóa theo thứ tự giống như tài liệu ( CTRL+ENTER => map[17] && map[13]
, KHÔNG map[13] && map[17]
). Bằng cách này, bạn sẽ không bao giờ bị nhầm lẫn khi bạn cần quay lại và chỉnh sửa mã.
Một gotcha với chuỗi if-other
Nếu kiểm tra các combo có số lượng khác nhau (như CtrlShiftAltEntervà CtrlEnter), hãy đặt các combo nhỏ hơn sau các combo lớn hơn, nếu không, các combo nhỏ hơn sẽ ghi đè lên các combo lớn hơn nếu chúng đủ tương tự. Thí dụ:
// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
alert('You found me');
}else if(map[13]){ // ENTER
alert('You pressed Enter. You win the prize!')
}
// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"
Gotcha: "Combo phím này tiếp tục kích hoạt mặc dù tôi không nhấn phím"
Khi xử lý các cảnh báo hoặc bất cứ điều gì lấy nét từ cửa sổ chính, bạn có thể muốn đưa map = []
vào để đặt lại mảng sau khi điều kiện được thực hiện. Điều này là do một số thứ, như alert()
, lấy tiêu điểm ra khỏi cửa sổ chính và khiến sự kiện 'keyup' không kích hoạt. Ví dụ:
if(map[17] && map[13]){ // CTRL+ENTER
alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:
if(map[17] && map[13]){ // CTRL+ENTER
alert('Take that, bug!');
map = {};
}
// The bug no longer happens since the array is cleared
Gotcha: Mặc định của trình duyệt
Đây là một điều khó chịu tôi tìm thấy, với giải pháp bao gồm:
Sự cố: Vì trình duyệt thường có các hành động mặc định trên các tổ hợp phím (như CtrlDkích hoạt cửa sổ dấu trang hoặc CtrlShiftCkích hoạt skynote trên maxthon), nên bạn cũng có thể muốn thêm return false
sau map = []
, vì vậy người dùng trang web của bạn sẽ không cảm thấy thất vọng khi "Tệp trùng lặp" chức năng, được đưa vào CtrlD, đánh dấu trang thay thế.
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Nếu không return false
, cửa sổ Bookmark sẽ bật lên, làm mất tinh thần của người dùng.
Tuyên bố trả lại (mới)
Được rồi, vì vậy bạn không luôn muốn thoát khỏi chức năng tại thời điểm đó. Đó là lý do tại sao event.preventDefault()
chức năng là ở đó. Những gì nó làm là đặt một cờ nội bộ thông báo cho trình thông dịch không cho phép trình duyệt chạy hành động mặc định của nó. Sau đó, việc thực thi chức năng tiếp tục (trong khi đó return
sẽ ngay lập tức thoát khỏi chức năng).
Hiểu sự khác biệt này trước khi bạn quyết định sử dụng return false
haye.preventDefault()
event.keyCode
không được chấp nhận
Người dùng SeanVieira đã chỉ ra trong các ý kiến không event.keyCode
được tán thành.
Ở đó, ông đã đưa ra một giải pháp thay thế tuyệt vời : event.key
, trả về một chuỗi đại diện của phím được nhấn, như "a"
cho Ahoặc "Shift"
choShift .
Tôi đã đi trước và nấu một công cụ để kiểm tra các chuỗi nói.
element.onevent
đấu với element.addEventListener
Trình xử lý đã đăng ký có addEventListener
thể được xếp chồng lên nhau và được gọi theo thứ tự đăng ký, trong khi cài đặt .onevent
trực tiếp khá tích cực và ghi đè bất cứ thứ gì bạn có trước đây.
document.body.onkeydown = function(ev){
// do some stuff
ev.preventDefault(); // cancels default actions
return false; // cancels this function as well as default actions
}
document.body.addEventListener("keydown", function(ev){
// do some stuff
ev.preventDefault() // cancels default actions
return false; // cancels this function only
});
Tài .onevent
sản dường như ghi đè lên tất cả mọi thứ và hành vi của ev.preventDefault()
và return false;
có thể khá khó đoán.
Trong cả hai trường hợp, trình xử lý được đăng ký thông qua addEventlistener
dường như dễ viết hơn và lý do về.
Ngoài ra còn có attachEvent("onevent", callback)
từ triển khai phi tiêu chuẩn của Internet Explorer, nhưng điều này vượt quá sự phản đối và thậm chí không liên quan đến JavaScript (nó liên quan đến một ngôn ngữ bí truyền gọi là JScript ). Bạn nên tránh mã polyglot càng nhiều càng tốt.
Một lớp người trợ giúp
Để giải quyết sự nhầm lẫn / khiếu nại, tôi đã viết một "lớp" thực hiện việc trừu tượng hóa này ( liên kết pastebin ):
function Input(el){
var parent = el,
map = {},
intervals = {};
function ev_kdown(ev)
{
map[ev.key] = true;
ev.preventDefault();
return;
}
function ev_kup(ev)
{
map[ev.key] = false;
ev.preventDefault();
return;
}
function key_down(key)
{
return map[key];
}
function keys_down_array(array)
{
for(var i = 0; i < array.length; i++)
if(!key_down(array[i]))
return false;
return true;
}
function keys_down_arguments()
{
return keys_down_array(Array.from(arguments));
}
function clear()
{
map = {};
}
function watch_loop(keylist, callback)
{
return function(){
if(keys_down_array(keylist))
callback();
}
}
function watch(name, callback)
{
var keylist = Array.from(arguments).splice(2);
intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
}
function unwatch(name)
{
clearInterval(intervals[name]);
delete intervals[name];
}
function detach()
{
parent.removeEventListener("keydown", ev_kdown);
parent.removeEventListener("keyup", ev_kup);
}
function attach()
{
parent.addEventListener("keydown", ev_kdown);
parent.addEventListener("keyup", ev_kup);
}
function Input()
{
attach();
return {
key_down: key_down,
keys_down: keys_down_arguments,
watch: watch,
unwatch: unwatch,
clear: clear,
detach: detach
};
}
return Input();
}
Lớp này không làm mọi thứ và nó sẽ không xử lý mọi trường hợp sử dụng có thể hiểu được. Tôi không phải là một người thư viện. Nhưng để sử dụng tương tác chung thì nó sẽ ổn.
Để sử dụng lớp này, hãy tạo một thể hiện và trỏ nó đến thành phần bạn muốn liên kết đầu vào bàn phím với:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
Những gì điều này sẽ làm là gắn một trình nghe đầu vào mới vào phần tử với #txt
(giả sử đó là một vùng văn bản) và đặt một điểm theo dõi cho tổ hợp phím Ctrl+5
. Khi cả hai Ctrl
và 5
không hoạt động, chức năng gọi lại mà bạn đã truyền vào (trong trường hợp này, một chức năng thêm "FIVE "
vào vùng văn bản) sẽ được gọi. Cuộc gọi lại được liên kết với tên print_5
, vì vậy để loại bỏ nó, bạn chỉ cần sử dụng:
input_txt.unwatch("print_5");
Để tách input_txt
khỏi txt
phần tử:
input_txt.detach();
Bằng cách này, bộ sưu tập rác có thể nhặt đối tượng ( input_txt
), nếu nó bị vứt đi và bạn sẽ không còn người nghe sự kiện zombie cũ.
Để tìm hiểu kỹ, đây là một tài liệu tham khảo nhanh về API của lớp, được trình bày theo kiểu C / Java để bạn biết họ trả về cái gì và những đối số họ mong đợi.
Boolean key_down (String key);
Trả về true
nếu key
là xuống, sai khác.
Boolean keys_down (String key1, String key2, ...);
Trả về true
nếu tất cả các phím key1 .. keyN
bị hỏng, sai khác.
void watch (String name, Function callback, String key1, String key2, ...);
Tạo một "điểm quan sát" sao cho nhấn tất cả keyN
sẽ kích hoạt cuộc gọi lại
void unwatch (String name);
Loại bỏ quan điểm cho biết thông qua tên của nó
void clear (void);
Xóa bộ nhớ cache "phím xuống". Tương đương như map = {}
trên
void detach (void);
Tách người nghe ev_kdown
và ev_kup
người nghe khỏi phần tử cha, giúp có thể thoát khỏi trường hợp một cách an toàn
Cập nhật 2017-12-02 Đáp lại yêu cầu xuất bản này lên github, tôi đã tạo ra một ý chính .
Cập nhật 2018-07-21 Tôi đã chơi với lập trình kiểu khai báo một thời gian và cách này hiện là sở thích cá nhân của tôi: fiddle , pastebin
Nói chung, nó sẽ hoạt động với các trường hợp bạn thực sự muốn (ctrl, alt, shift), nhưng nếu bạn cần nhấn, a+w
đồng thời, sẽ không quá khó để "kết hợp" các cách tiếp cận vào một tra cứu đa khóa.
Tôi hy vọng câu trả lời này được giải thích cặn kẽ mini-blog là hữu ích :)