Как определить, нажимается ли одновременно несколько клавиш с помощью JavaScript?
Я пытаюсь разработать игровой движок JavaScript, и я столкнулся с этой проблемой:
- Когда я нажимаю пробел, персонаж прыгает.
- Когда я нажимаю → персонаж движется вправо.
Проблема в том, что когда я нажимаю вправо, а затем нажимаю пробел, персонаж прыгает, а затем перестает двигаться.
Я использую keydown
функция, чтобы получить клавишу нажатой. Как я могу проверить, нажаты ли несколько клавиш одновременно?
19 ответов
Обнаружение нескольких нажатий клавиш легко, если вы понимаете концепцию
То, как я это делаю, выглядит так:
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 */
}
Этот код очень прост: поскольку компьютер одновременно выполняет только одно нажатие клавиши, создается массив для отслеживания нескольких клавиш. Затем этот массив можно использовать для проверки одного или нескольких ключей одновременно.
Просто чтобы объяснить, скажем, вы нажимаете А и В, каждый из которых запускает keydown
событие, которое устанавливает map[e.keyCode]
к стоимости e.type == keydown
, который оценивается как истина или ложь. Теперь оба map[65]
а также map[66]
установлены в true
, Когда вы отпустите A
, keyup
события, в результате чего та же логика определяет противоположный результат для map[65]
(А), который теперь является ложным, но так как map[66]
(B) все еще "вниз" (это не вызвало событие keyup), это остается верным.
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's $()
более широко рекомендуется.
Просто убедитесь, что тег script стоит после тела HTML. Совет по оптимизации. Большинство известных сайтов ставят тег script после тега body для оптимизации. Это связано с тем, что тег script блокирует дальнейшую загрузку элементов до завершения загрузки скрипта. Размещение его перед контентом позволяет загружать контент заранее.
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');
}
}
(конец редактирования)
В этом примере проверяются клавиши Ctrl Shift A, Ctrl Shift B и Ctrl Shift C
Это так же просто, как это:)
Заметки
Отслеживание ключевых кодов
Как правило, рекомендуется документировать код, особенно такие, как коды клавиш (например, // CTRL+ENTER
) так что вы можете вспомнить, что они были.
Вы также должны поместить коды клавиш в том же порядке, что и документация (CTRL+ENTER => map[17] && map[13]
НЕ map[13] && map[17]
). Таким образом, вы никогда не запутаетесь, когда вам нужно вернуться и отредактировать код.
Гоча с цепями if-else
Если вы проверяете комбинации разных сумм (например, Ctrl Shift Alt Enter и Ctrl Enter), поместите меньшие комбо после больших комбо, иначе меньшие комбо заменит более крупные комбинации, если они достаточно похожи. Пример:
// 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"
Попался: "Эта комбинация клавиш продолжает активироваться, хотя я не нажимаю клавиши"
При работе с оповещениями или чем-либо, что фокусируется из главного окна, вы можете включить map = []
сбросить массив после выполнения условия. Это потому, что некоторые вещи, такие как alert()
уберите фокус с главного окна и не вызывайте событие keyup. Например:
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
Получил: браузер по умолчанию
Вот неприятная вещь, которую я нашел, с включенным решением:
Проблема: поскольку браузер обычно выполняет действия по умолчанию для сочетаний клавиш (например, Ctrl D активирует окно закладок или Ctrl Shift C активирует skynote в maxthon), вы также можете добавить return false
после map = []
Таким образом, пользователи вашего сайта не будут разочарованы, когда функция "Duplicate File", помещенная в Ctrl D, вместо этого добавит в закладки страницу.
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
устарела
Пользователь Sean Vieira указал в комментариях, что event.keyCode
устарела.
Там он дал отличную альтернативу: event.key
, который возвращает строковое представление нажатой клавиши, как "a"
для А или "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 Некоторое время я играл с декларативным программированием стилей, и теперь этот путь мой любимый: fiddle, pastebin
Как правило, он будет работать с теми случаями, которые вы реально хотите (ctrl, alt, shift), но если вам нужно нажать, скажем, a+w
в то же время было бы не так сложно "объединить" подходы в многоключевой поиск.
Надеюсь, этот подробный мини-блог с ответом был полезен:)
document.onkeydown = keydown;
function keydown (evt) {
if (!evt) evt = event;
if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) {
alert("CTRL+ALT+F4");
} else if (evt.shiftKey && evt.keyCode === 9) {
alert("Shift+TAB");
}
}
Вы должны использовать событие keydown, чтобы отслеживать нажатые клавиши, и вы должны использовать событие keyup, чтобы отслеживать, когда клавиши отпущены.
Смотрите этот пример: http://jsfiddle.net/vor0nwe/mkHsU/
(Обновление: я воспроизводю код здесь, на случай, если jsfiddle.net не поможет:) HTML:
<ul id="log">
<li>List of keys:</li>
</ul>
... и Javascript (используя jQuery):
var log = $('#log')[0],
pressedKeys = [];
$(document.body).keydown(function (evt) {
var li = pressedKeys[evt.keyCode];
if (!li) {
li = log.appendChild(document.createElement('li'));
pressedKeys[evt.keyCode] = li;
}
$(li).text('Down: ' + evt.keyCode);
$(li).removeClass('key-up');
});
$(document.body).keyup(function (evt) {
var li = pressedKeys[evt.keyCode];
if (!li) {
li = log.appendChild(document.createElement('li'));
}
$(li).text('Up: ' + evt.keyCode);
$(li).addClass('key-up');
});
В этом примере я использую массив для отслеживания нажатия клавиш. В реальном приложении вы можете захотеть delete
каждый элемент после того, как их связанный ключ был выпущен.
Обратите внимание, что, хотя я использовал jQuery, чтобы упростить для себя задачу в этом примере, концепция работает так же хорошо при работе в "сыром" Javascript.
Для кого нужен полный пример кода. Правый + левый добавлен
var keyPressed = {};
document.addEventListener('keydown', function(e) {
keyPressed[e.key + e.location] = true;
if(keyPressed.Shift1 == true && keyPressed.Control1 == true){
// Left shift+CONTROL pressed!
keyPressed = {}; // reset key map
}
if(keyPressed.Shift2 == true && keyPressed.Control2 == true){
// Right shift+CONTROL pressed!
keyPressed = {};
}
}, false);
document.addEventListener('keyup', function(e) {
keyPressed[e.key + e.location] = false;
keyPressed = {};
}, false);
Это не универсальный метод, но в некоторых случаях он может быть полезен. Это полезно для комбинаций, как CTRL+ something или Shift+ something или CTRL+ Shift+ something, и т.д.
Пример: если вы хотите распечатать страницу с помощью CTRL+ P, за первой нажатой клавишей всегда CTRL следует P. То же самое с CTRL+ S, CTRL+ U и другими комбинациями.
document.addEventListener('keydown',function(e){
//SHIFT + something
if(e.shiftKey){
switch(e.code){
case 'KeyS':
console.log('Shift + S');
break;
}
}
//CTRL + SHIFT + something
if(e.ctrlKey && e.shiftKey){
switch(e.code){
case 'KeyS':
console.log('CTRL + Shift + S');
break;
}
}
});
Я использовал этот способ (должен был проверить, где нажата Shift + Ctrl):
// create some object to save all pressed keys
var keys = {
shift: false,
ctrl: false
};
$(document.body).keydown(function(event) {
// save status of the button 'pressed' == 'true'
if (event.keyCode == 16) {
keys["shift"] = true;
} else if (event.keyCode == 17) {
keys["ctrl"] = true;
}
if (keys["shift"] && keys["ctrl"]) {
$("#convert").trigger("click"); // or do anything else
}
});
$(document.body).keyup(function(event) {
// reset status of the button 'released' == 'false'
if (event.keyCode == 16) {
keys["shift"] = false;
} else if (event.keyCode == 17) {
keys["ctrl"] = false;
}
});
Мне нравится использовать этот фрагмент, он очень полезен для написания скриптов ввода в игру.
var keyMap = [];
window.addEventListener('keydown', (e)=>{
if(!keyMap.includes(e.keyCode)){
keyMap.push(e.keyCode);
}
})
window.addEventListener('keyup', (e)=>{
if(keyMap.includes(e.keyCode)){
keyMap.splice(keyMap.indexOf(e.keyCode), 1);
}
})
function key(x){
return (keyMap.includes(x));
}
function checkGameKeys(){
if(key(32)){
// Space Key
}
if(key(37)){
// Left Arrow Key
}
if(key(39)){
// Right Arrow Key
}
if(key(38)){
// Up Arrow Key
}
if(key(40)){
// Down Arrow Key
}
if(key(65)){
// A Key
}
if(key(68)){
// D Key
}
if(key(87)){
// W Key
}
if(key(83)){
// S Key
}
}
$(document).ready(function () {
// using ascii 17 for ctrl, 18 for alt and 83 for "S"
// ctr+alt+S
var map = { 17: false, 18: false, 83: false };
$(document).keyup(function (e) {
if (e.keyCode in map) {
map[e.keyCode] = true;
if (map[17] && map[18] && map[83]) {
// Write your own code here, what you want to do
map[17] = false;
map[18] = false;
map[83] = false;
}
}
else {
// if u press any other key apart from that "map" will reset.
map[17] = false;
map[18] = false;
map[83] = false;
}
});
});
Заставьте клавишу даже вызывать несколько функций, каждая из которых проверяет определенную клавишу и реагирует соответствующим образом.
document.keydown = function (key) {
checkKey("x");
checkKey("y");
};
Если кому-то нужно простое решение.
let keys = [];
document.addEventListener("keydown", (e) => {
keys.push(e.key);
if (keys.includes("Control") && keys.includes("o")) {
console.log("open");
}
if (keys.includes("Control") && keys.includes("s")) {
console.log("save");
}
});
// clear the keys array
document.addEventListener("keyup", () => {
keys = [];
});
Я бы попробовал добавить keypress
Event
обработчик на keydown
, Например:
window.onkeydown = function() {
// evaluate key and call respective handler
window.onkeypress = function() {
// evaluate key and call respective handler
}
}
window.onkeyup = function() {
window.onkeypress = void(0) ;
}
Это просто для иллюстрации шаблона; Я не буду вдаваться в подробности здесь (особенно в браузер уровня 2+) Event
постановка на учет).
Отпишитесь, пожалуйста, помогает ли это или нет.
Если одна из нажатых клавиш Alt / Crtl / Shift, вы можете использовать этот метод:
document.body.addEventListener('keydown', keysDown(actions) );
function actions() {
// do stuff here
}
// simultaneous pressing Alt + R
function keysDown (cb) {
return function (zEvent) {
if (zEvent.altKey && zEvent.code === "KeyR" ) {
return cb()
}
}
}
если вы хотите найти какое-либо событие нажатия клавиши с помощью клавиши управления, вы можете сделать это так
onkeypress = (e) =>{
console.log(e);
if(e.ctrlKey && e.code == "KeyZ"){
document.write("do somthing")
} }
case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);
if(pj > 0) {
ABFunction();
pj = 0;
}
break;
case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);
if(jp > 0) {
ABFunction();
jp = 0;
}
break;
Не самый лучший способ, я знаю.
Просто делаем что-то более стабильное:
var keys = [];
$(document).keydown(function (e) {
if(e.which == 32 || e.which == 70){
keys.push(e.which);
if(keys.length == 2 && keys.indexOf(32) != -1 && keys.indexOf(70) != -1){
alert("it WORKS !!"); //MAKE SOMETHING HERE---------------->
keys.length = 0;
}else if((keys.indexOf(32) == -1 && keys.indexOf(70) != -1) || (keys.indexOf(32) != -1 && keys.indexOf(70) == -1) && (keys.indexOf(32) > 1 || keys.indexOf(70) > 1)){
}else{
keys.length = 0;
}
}else{
keys.length = 0;
}
});
Для тех, кто использует React, вот мое решение:
import { useEffect, useState } from "react";
import Backdrop from '@mui/material/Backdrop';
export const Example = () => {
const [backdropOpen, setBackdropOpen] = useState(false);
useEffect(() => {
// Keys that need to be pressed at the same time in order for
// the 'backdropOpen' variable to be 'true'
const keysArr = ['ControlLeft', 'ShiftLeft', 'AltLeft'];
const keysMap = {};
let backdropOpenLocal = false;
const keydownEvent = 'keydown';
const keyupEvent = 'keyup';
const checkKeys = () => {
const keysArePressed = keysArr.every((value) => keysMap[value] === keydownEvent);
if (keysArePressed !== backdropOpenLocal) {
backdropOpenLocal = keysArePressed;
setBackdropOpen(keysArePressed);
}
}
const handleKeyDown = (event) => {
const keyCode = event.code;
if (keysArr.includes(keyCode) && keysMap[keyCode] !== keydownEvent) {
keysMap[keyCode] = keydownEvent;
}
checkKeys();
}
const handleKeyUp = (event) => {
const keyCode = event.code;
if (keysArr.includes(keyCode) && keysMap[keyCode] !== keyupEvent) {
keysMap[keyCode] = keyupEvent;
}
checkKeys();
}
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
}
}, []);
return (
<React.Fragmemnt>
<div>
<Backdrop
open={backdropOpen}
>
<span>
It worked!
</span>
</Backdrop>
</div>
</React.Fragmemnt>
);
}
Имейте в виду, что нам нужно использоватьbackdropOpenLocal
а не внутри функции, потому что мы хотим обновить только локальную переменную области видимости и сохранить состояние области видимости.
Если мы обновим состояние компонента Example и попытаемся получить доступ, у нас будет то же значение, что и раньше, если только мы не передадимbackdropOpen
в массиве зависимостей ; это приведет к тому, что переменные области видимости внутриuseEffect
для сброса, а мы этого не хотим.
я использую случаи, если и bools. У меня был проект, и это отлично сработало для меня.
window.addEventListener("keydown", onKeyDown, false);
window.addEventListener("keyup", onKeyUp, false);
function onKeyDown(event) {
var keyCode = event.keyCode;
switch (keyCode) {
case 68: //D
keyd = true;
break;
case 32: //spaaaaaaaaaaaaaaace
keyspace = true;
break;
case 65: //A
keya = true;
break;
case 37:
keya = true;
break;
case 38:
keyspace = true;
break;
case 39:
keyd = true;
break;
}
}
function onKeyUp(event) {
var keyCode = event.keyCode;
switch (keyCode) {
case 68: //dddddd
keyd = false;
break;
case 32: //spaaaaaaaaaaaaaaaaaaaaaace
keyspace = false;
break;
case 65: //aaaaa
keya = false;
break;
case 37:
keya = false;
break;
case 38:
keyspace = false;
break;
case 39:
keyd = false;
break;
}
}
Easiest, and most Effective Method
//check key press
function loop(){
//>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
if(map[*key*]==true){
//do something
}
//multiple keys
if(map["x"]==true&&map["ctrl"]==true){
console.log("x, and ctrl are being held down together")
}
}
//>>>variable which will hold all key information<<
var map={}
//Key Event Listeners
window.addEventListener("keydown", btnd, true);
window.addEventListener("keyup", btnu, true);
//Handle button down
function btnd(e) {
map[e.key] = true;
}
//Handle Button up
function btnu(e) {
map[e.key] = false;
}
//>>>If you want to see the state of every Key on the Keybaord<<<
setInterval(() => {
for (var x in map) {
log += "|" + x + "=" + map[x];
}
console.log(log);
log = "";
}, 300);