Примітка: keyCode тепер застарілий.
Виявлення кількох натискань клавіш легко, якщо ви розумієте концепцію
Я це роблю так:
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 */
}
Цей код дуже простий: оскільки комп'ютер одночасно пропускає один натискання клавіш, створюється масив для відстеження кількох клавіш. Потім масив може бути використаний для перевірки одночасно однієї або декількох клавіш.
Скажімо, для пояснення, скажімо, ви натискаєте Aта B, кожен запускає keydown
подію, яка встановлюється map[e.keyCode]
на значення e.type == keydown
, яке оцінюється як істинне, або хибне . Тепер обидва map[65]
і map[66]
налаштовані на true
. Коли ви відпустите A
, keyup
подія запускається, викликаючи тією ж логікою, щоб визначити протилежний результат для map[65]
(A), який зараз помилковий , але оскільки map[66]
(B) все ще "вниз" (це не викликало події клавіатури), це залишається правдою .
map
Масив, через обидві події, виглядає наступним чином :
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
Зараз ви можете зробити дві речі:
A) Ключовий реєстратор ( приклад ) може бути створений як довідник для подальшого, коли потрібно швидко з'ясувати один або кілька ключових кодів. Припустимо, що ви визначили елемент html і вказали на нього зі змінною element
.
element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
if(map[i]){
element.innerHTML += '<hr>' + i;
}
}
Примітка. Ви можете легко схопити елемент за його id
атрибутом.
<div id="element"></div>
Це створює html-елемент, на який можна легко посилатися в JavaScript element
alert(element); // [Object HTMLDivElement]
Вам навіть не потрібно користуватися document.getElementById()
або $()
захоплювати його. Але для сумісності $()
більше рекомендується використовувати jQuery .
Просто переконайтеся, що тег скрипту надходить після основи HTML. Порада з оптимізації : Більшість веб-сайтів із великими іменами ставлять тег сценарію після тегу body для оптимізації. Це відбувається тому, що тег скрипта блокує додаткові елементи від завантаження до завершення завантаження сценарію. Поміщення його попереду вмісту дозволяє заздалегідь завантажити вміст.
B (саме там і полягає ваш інтерес) Ви можете перевірити наявність одного чи декількох ключів у той час, де /*insert conditional here*/
був, скористайтеся цим прикладом:
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');
}
Редагувати : це не самий читабельний фрагмент. Читання важливо, тому ви можете спробувати щось подібне, щоб полегшити очі:
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;
}
Використання:
test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('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');
}
}
(кінець редагування)
Цей приклад перевіряє наявність CtrlShiftA, CtrlShiftBіCtrlShiftC
Це так просто, як це :)
Примітки
Відстеження KeyCodes
Як правило, добре документувати код, особливо такі речі, як Ключові коди (наприклад // CTRL+ENTER
), щоб ви могли запам'ятати, якими вони були.
Ви також повинні поставити ключові коди в тому ж порядку, що і документація ( CTRL+ENTER => map[17] && map[13]
, НЕ map[13] && map[17]
). Таким чином ви ніколи не заплутаєтесь, коли вам потрібно буде повернутися назад і відредагувати код.
Гатча з ланцюжками if-else
Якщо ви перевіряєте комбінації різної кількості (наприклад, CtrlShiftAltEnterта CtrlEnter), поставте менші комбо після більших комбо, інакше менші комбо замінять більші комбо, якщо вони досить схожі. Приклад:
// 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: "Цей комбінація клавіш продовжує активуватися, хоча я не натискаю клавіші"
Якщо ви маєте справу з сповіщеннями або будь-чим, що фокусується в головному вікні, ви можете включити map = []
для скидання масиву після виконання умови. Це тому, що деякі речі, як-от alert()
, відводять фокус від головного вікна і не спричиняють події "клавіатури". Наприклад:
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: Параметри браузера
Ось ось яку дратівливу річ я знайшов, включаючи рішення:
Проблема: Оскільки браузер зазвичай виконує дії за замовчуванням на ключових комбо (наприклад, CtrlDактивує вікно закладок або CtrlShiftCактивує знімок на макстоні), ви можете також додати return false
після цього map = []
, так що користувачі вашого сайту не будуть засмучені, коли "Копіювати файл" Функція, надіслана CtrlD, замість цього робить закладки на сторінку.
if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}
Без return false
вікна Закладки буде спливало, до жаху користувача.
Звіт про повернення (новий)
Гаразд, тому ви не завжди хочете вийти з функції в цей момент. Ось чому event.preventDefault()
функція є. Що він робить, це встановити внутрішній прапор, який вказує перекладачеві не дозволяти браузеру виконувати свої дії за замовчуванням. Після цього виконання функції продовжується (тоді як return
негайно вийде з функції).
Зрозумійте це відмінність , перш ніж вирішити , слід використовувати return false
абоe.preventDefault()
event.keyCode
застаріло
Користувач SeanVieira зазначив у коментарях, що event.keyCode
застарів.
Там він дав чудову альтернативу: event.key
яка повертає рядкове представлення натискання клавіші, як "a"
для A, або "Shift"
для Shift.
Я пішов вперед і приготував інструмент для вивчення згаданих рядків.
element.onevent
проти element.addEventListener
Обробники, зареєстровані на яких, addEventListener
можуть бути складені, і викликаються в порядку реєстрації, тоді як налаштування .onevent
безпосередньо є досить агресивним і перекриває все, що ви мали раніше.
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
Властивість здається перевизначити всі і поведінку , ev.preventDefault()
і return false;
може бути вельми непередбачуваним.
В будь-якому випадку, обробникам, зареєстрованим через, addEventlistener
здається, простіше писати та міркувати.
Існує також attachEvent("onevent", callback)
нестандартна реалізація Internet Explorer, але це поза устареним і навіть не стосується JavaScript (стосується езотеричної мови під назвою JScript ). Вам було б в інтересах максимально уникати поліглот-коду.
Клас помічників
Щоб вирішити проблему, пов'язану з плутаниною / скаргами, я написав "клас", який виконує цю абстракцію ( пастбін-посилання ):
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();
}
Цей клас не робить все, і він не обробляє кожен можливий випадок використання. Я не хлопець з бібліотеки. Але для загального інтерактивного використання це повинно бути добре.
Щоб використовувати цей клас, створіть примірник і вкажіть його на елемент, з яким потрібно пов’язати введення клавіатури:
var input_txt = Input(document.getElementById("txt"));
input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");
Для цього потрібно приєднати новий елемент слухача вводу до елемента #txt
(припустимо, це текстовий пояс) і встановити точку спостереження для комбінації клавіш Ctrl+5
. Коли обидва Ctrl
і не 5
працюють, "FIVE "
буде викликана функція зворотного виклику, яку ви передали (у цьому випадку функція, яка додає до текстової області). Зворотний виклик пов'язаний з ім'ям print_5
, тому, щоб його видалити, ви просто використовуєте:
input_txt.unwatch("print_5");
Щоб відірватися input_txt
від txt
елемента:
input_txt.detach();
Таким чином, збирання сміття може забрати предмет ( input_txt
), якщо його викинути, і у вас не залишиться старий слухач подій зомбі.
Для детальності, ось короткий посилання на API класу, представлений у стилі C / Java, щоб ви знали, що вони повертають та які аргументи очікують.
Boolean key_down (String key);
Повертається, true
якщо key
вниз, помилка помилка.
Boolean keys_down (String key1, String key2, ...);
Повертається, true
якщо всі клавіші key1 .. keyN
відключені, інакше помилково.
void watch (String name, Function callback, String key1, String key2, ...);
Створюється "точка спостереження" така, що натискання на все keyN
викликає зворотний зв'язок
void unwatch (String name);
Видаляє вказану точку спостереження через її назву
void clear (void);
Витирає кеш "клавіші вниз". Еквівалентно map = {}
вище
void detach (void);
Відлучає батьківський елемент ev_kdown
і ev_kup
слухачів від нього, що дозволяє безпечно позбутися екземпляра
Оновити 2017-12-02 У відповідь на прохання опублікувати це в github, я створив суть .
Оновлення 2018-07-21 Я деякий час грав із програмою декларативної стилістики, і цей спосіб зараз є моїм улюбленим: скрипка , пастбін
Як правило, він буде працювати з справами, які ви хотіли б реально (ctrl, alt, shift), але якщо вам потрібно натиснути, скажімо, a+w
в той же час, "не поєднати" підходи в пошук декількома ключами.
Я сподіваюся, що цей ґрунтовно пояснений відповідь міні-блогу був корисним :)