Uwaga: kod keyCode jest teraz przestarzały.
Wykrywanie wielokrotnych naciśnięć klawiszy jest łatwe, jeśli zrozumiesz tę koncepcję
Sposób, w jaki to robię, wygląda następująco:
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 */
}
Ten kod jest bardzo prosty: ponieważ komputer wykonuje tylko jedno naciśnięcie klawisza na raz, tworzona jest tablica, aby śledzić wiele kluczy. Tablicy można następnie użyć do sprawdzenia jednego lub więcej kluczy naraz.
Aby to wyjaśnić, powiedzmy, że naciskasz Ai B, każdy wywołuje keydown
zdarzenie, które ustawia map[e.keyCode]
wartość e.type == keydown
, co daje wartość true lub false . Teraz oba map[65]
i map[66]
są ustawione na true
. Kiedy puścisz A
, keyup
zdarzenie zostaje wyzwolone, powodując, że ta sama logika określa przeciwny wynik dla map[65]
(A), który jest teraz fałszywy , ale ponieważ map[66]
(B) jest nadal „w dół” (nie wywołał zdarzenia klucza), pozostaje prawdą .
map
Array, dzięki obu imprez, wygląda następująco:
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Możesz teraz zrobić dwie rzeczy:
A) Rejestrator kluczy ( przykład ) można utworzyć jako odniesienie na później, gdy chcesz szybko znaleźć jeden lub więcej kodów kluczy. Zakładając, że zdefiniowałeś element html i wskazałeś na niego zmienną element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Uwaga: możesz łatwo pobrać element według jego id
atrybutu.
<div id="element"></div>
Tworzy to element html, do którego można łatwo odwołać się w javascript za pomocą element
alert(element); // [Object HTMLDivElement]
Nie musisz go nawet używać document.getElementById()
ani $()
chwytać. Jednak ze względu na kompatybilność $()
szerzej zalecane jest używanie jQuery .
Po prostu upewnij się, że tag skryptu znajduje się po treści kodu HTML. Wskazówka dotycząca optymalizacji : większość znanych witryn umieszcza tag script po tagu body w celu optymalizacji. Dzieje się tak, ponieważ znacznik skryptu blokuje ładowanie dalszych elementów do czasu zakończenia pobierania skryptu. Umieszczenie go przed treścią umożliwia wcześniejsze załadowanie treści.
B (gdzie leży twoje zainteresowanie) Możesz sprawdzić jeden lub więcej kluczy w tym samym czasie, w którym /*insert conditional here*/
był, weź ten przykład:
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');
}
Edycja : to nie jest najbardziej czytelny fragment. Czytelność jest ważna, więc możesz spróbować czegoś takiego, aby ułatwić oczy:
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;
}
Stosowanie:
test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')
Czy to jest lepsze?
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');
}
}
(koniec edycji)
Ten przykład sprawdza CtrlShiftA, CtrlShiftBorazCtrlShiftC
To takie proste :)
Uwagi
Śledzenie kodów kluczy
Zasadniczo dobrą praktyką jest dokumentowanie kodu, zwłaszcza takich rzeczy, jak kody kluczy (np. // CTRL+ENTER
), Abyś mógł zapamiętać, jakie to były.
Powinieneś również umieścić kody kluczy w tej samej kolejności, co w dokumentacji ( CTRL+ENTER => map[17] && map[13]
NIE map[13] && map[17]
). W ten sposób nigdy nie będziesz zdezorientowany, gdy będziesz musiał wrócić i edytować kod.
Gotcha z łańcuchami if-else
Jeśli sprawdzasz kombinacje o różnych ilościach (jak CtrlShiftAltEnteri CtrlEnter), umieść mniejsze kombinacje po większych kombinacjach, w przeciwnym razie mniejsze kombinacje zastąpią większe kombinacje, jeśli są wystarczająco podobne. Przykład:
// 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"
Rozumiem: „Ta kombinacja klawiszy ciągle się aktywuje, mimo że nie naciskam klawiszy”
W przypadku alertów lub czegokolwiek, co jest aktywowane w oknie głównym, możesz chcieć włączyć map = []
resetowanie tablicy po spełnieniu warunku. Dzieje się tak, ponieważ niektóre rzeczy, na przykład alert()
, odciągają fokus od głównego okna i powodują, że zdarzenie „keyup” nie jest wyzwalane. Na przykład:
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: ustawienia domyślne przeglądarki
Oto irytująca rzecz, którą znalazłem, z dołączonym rozwiązaniem:
Problem: Ponieważ przeglądarka zwykle ma domyślne działania na kombinacjach klawiszy (np. CtrlDAktywuje okno zakładek lub CtrlShiftCaktywuje skynote na maxthonie), możesz też chcieć dodać return false
później map = []
, aby użytkownicy Twojej witryny nie byli sfrustrowani, gdy „Duplikat pliku” funkcja, CtrlDzamiast tego tworzy zakładki do strony.
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Bez return false
okno zakładek będzie pop-up, ku przerażeniu użytkownika.
Instrukcja return (nowa)
Okay, więc nie zawsze chcesz wyjść z funkcji w tym momencie. Dlatego event.preventDefault()
jest tam funkcja. To, co robi, to ustawienie wewnętrznej flagi, która mówi interpreterowi, aby nie zezwalał przeglądarce na wykonanie jej domyślnej akcji. Po tym wykonywanie funkcji jest kontynuowane (natomiast return
natychmiast opuści funkcję).
Zrozum to rozróżnienie, zanim zdecydujesz, czy użyć return false
lube.preventDefault()
event.keyCode
jest przestarzałe
Użytkownik SeanVieira wskazał w komentarzach, który event.keyCode
jest przestarzały.
Tam podał doskonałą alternatywę event.key
:, która zwraca ciąg znaków reprezentujący naciśnięty klawisz, na przykład "a"
for Alub "Shift"
for Shift.
Poszedłem do przodu i przygotowałem narzędzie do badania wspomnianych strun.
element.onevent
vs element.addEventListener
Handlery zarejestrowane za pomocą addEventListener
można łączyć i wywoływać w kolejności rejestracji, podczas gdy .onevent
bezpośrednie ustawienie jest raczej agresywne i zastępuje wszystko, co miałeś wcześniej.
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
});
.onevent
Nieruchomość wydaje się przesłaniać wszystko i zachowanie ev.preventDefault()
i return false;
może być dość nieprzewidywalne.
W obu przypadkach obsługa zarejestrowana za pośrednictwem addEventlistener
wydaje się być łatwiejsza do napisania i uzasadnienia.
Istnieje również attachEvent("onevent", callback)
niestandardowa implementacja Internet Explorera, ale jest to przestarzałe i nie dotyczy nawet JavaScript (dotyczy to ezoterycznego języka zwanego JScript ). Byłoby w twoim najlepszym interesie unikanie kodu poliglotowego tak bardzo, jak to możliwe.
Klasa pomocnicza
Aby rozwiązać problem / skargi, napisałem „klasę”, która wykonuje tę abstrakcję ( link wklejania ):
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();
}
Ta klasa nie robi wszystkiego i nie poradzi sobie z każdym możliwym przypadkiem użycia. Nie jestem facetem z biblioteki. Ale do ogólnego użytku interaktywnego powinno być dobrze.
Aby użyć tej klasy, utwórz instancję i wskaż element, z którym chcesz skojarzyć wprowadzanie z klawiatury:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
To spowoduje dołączenie nowego odbiornika wejściowego do elementu z #txt
(załóżmy, że jest to obszar tekstowy) i ustawienie punktu obserwacyjnego dla kombinacji klawiszy Ctrl+5
. Gdy oba Ctrl
i 5
są wyłączone, "FIVE "
zostanie wywołana funkcja zwrotna, którą przekazałeś (w tym przypadku funkcja, która dodaje do obszaru tekstowego). Callback jest powiązany z nazwą print_5
, więc aby je usunąć, wystarczy użyć:
input_txt.unwatch("print_5");
Aby odłączyć input_txt
od txt
elementu:
input_txt.detach();
W ten sposób odśmiecanie może odebrać obiekt ( input_txt
), jeśli zostanie wyrzucony, i nie pozostanie stary odbiornik zdarzeń zombie.
Dla dokładności, oto krótkie odniesienie do API klasy, przedstawione w stylu C / Java, abyś wiedział, co zwracają i jakich argumentów oczekują.
Boolean key_down (String key);
Zwraca true
jeśli key
jest wyłączone, w przeciwnym razie false.
Boolean keys_down (String key1, String key2, ...);
Zwraca, true
jeśli wszystkie klucze key1 .. keyN
są wyłączone, w przeciwnym razie false.
void watch (String name, Function callback, String key1, String key2, ...);
Tworzy "punkt obserwacyjny" taki, że naciśnięcie wszystkich keyN
wywoła wywołanie zwrotne
void unwatch (String name);
Usuwa wspomniany punkt obserwacyjny poprzez jego nazwę
void clear (void);
Czyści pamięć podręczną „kluczy w dół”. Odpowiednik map = {}
powyżej
void detach (void);
Odłącza detektory ev_kdown
i ev_kup
od elementu nadrzędnego, umożliwiając bezpieczne pozbycie się instancji
Aktualizacja 2017-12-02 W odpowiedzi na prośbę o opublikowanie tego na github stworzyłem streszczenie .
Aktualizacja 2018-07-21 Od jakiegoś czasu bawię się programowaniem w stylu deklaratywnym, a teraz jest to mój ulubiony: skrzypce , pastebin
Generalnie będzie działać z przypadkami, które chcesz realistycznie (ctrl, alt, shift), ale jeśli musisz uderzyć, powiedzmy, a+w
w tym samym czasie, nie byłoby zbyt trudno „połączyć” podejścia w wyszukiwanie wieloprzyciskowe.
Mam nadzieję, że ten dogłębnie wyjaśniony mini blog okazał się pomocny :)