如何检测如果多个键被按下一次使用JavaScript?

我试图开发一个JavaScript游戏引擎,我遇到了这个问题:

  • 当我按空间时,字符跳转。
  • 当我按时,字符向右移动。

问题是,当我按右键,然后按空格键时,角色会跳跃,然后停止移动。

我使用keydown函数来按下键。如何检查是否同时按下了多个键?

215736 次浏览

我会尝试在keydown上添加一个keypress Event处理程序。例句:

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) ;
}

这只是为了说明一个模式;我不会在这里详细说明(特别是不涉及特定于浏览器的level2+ Event注册)。

请回复这是否有帮助。

你应该使用keydown事件来跟踪按下的按键,你应该使用按键弹起事件来跟踪按键释放的时间。

请看这个例子:http://jsfiddle.net/vor0nwe/mkHsU/

(更新:我在这里复制代码,以防jsfiddle.net保释:) HTML: < / p >

<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中工作时,这个概念也同样有效。

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;

这不是最好的方法,我知道。

注意: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 */
}

这段代码非常简单:由于计算机一次只传递一个击键,因此创建一个数组来跟踪多个键。然后可以使用该数组一次检查一个或多个键。

为了解释一下,假设你按下map[e.keyCode]0和map[e.keyCode]1,每个都触发一个keydown事件,将map[e.keyCode]设置为e.type == keydown的值,其计算结果为map[e.keyCode]2或map[e.keyCode]3。现在map[65]map[66]都被设置为true。当你释放A时,keyup事件触发,导致相同的逻辑为map[65] (A)确定相反的结果,现在是map[e.keyCode]3,但由于map[66] (B)仍然是“down”;(它没有触发keyup事件),它仍然是map[e.keyCode]2。

map数组,通过这两个事件,看起来像这样:

// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]

现在你可以做两件事:

一) Key logger (例子)可以创建为稍后当您想快速找出一个或多个键代码时的参考。假设你已经定义了一个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正文之后。优化技巧:大多数大牌网站把脚本标签作为优化的正文标签。这是因为在脚本下载完成之前,script标记阻止加载更多的元素。把它放在内容的前面可以让内容提前加载。

你可以在/*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转变一个Ctrl转变BCtrl转变C

就是这么简单:)

笔记

跟踪键盘代码

作为一般规则,记录代码是一个很好的实践,尤其是像Key code(比如 // CTRL+ENTER)这样的东西,这样你就可以记住它们是什么。

你还应该将关键代码放在与文档相同的顺序(CTRL+ENTER => map[17] && map[13], NOT map[13] && map[17])。这样,当您需要返回并编辑代码时,就不会感到困惑。

用if-else链抓住你

如果检查数量不同的组合(如Ctrl转变Alt输入Ctrl输入),将较小的组合较大的组合,否则如果它们足够相似,较小的组合将覆盖较大的组合。例子:

// 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

明白了:浏览器默认值

下面是我发现的一个恼人的问题,解决方案包括:

问题:由于浏览器通常对键组合有默认操作(如CtrlD激活书签窗口,或Ctrl转变C激活maxthon上的skynote),你可能还想在map = []之后添加return false,这样当“重复文件”出现时,你的网站用户就不会感到沮丧。函数,被放在CtrlD上,则将该页标记为书签。

if(map[17] && map[68]){ // CTRL+D
alert('The bookmark window didn\'t pop up!');
map = {};
return false;
}

如果没有return false,书签窗口就会弹出,这让用户很沮丧。

return语句(new)

你并不总是想在那个点退出函数。这就是event.preventDefault()函数存在的原因。它所做的是设置一个内部标志,告诉解释器允许浏览器运行默认操作。之后,函数继续执行(而return将立即退出函数)。

在决定是使用return false还是e.preventDefault()之前,请先理解这个区别

event.keyCode已弃用

用户SeanVieira在评论中指出,event.keyCode已弃用。

在那里,他给出了一个很好的替代方案:event.key,它返回被按下的键的字符串表示形式,例如"a"表示一个,或"Shift"表示转变

我继续编写了一个工具来检查这些字符串。

element.onevent vs 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注册的处理程序似乎更容易编写和推理。

还有来自Internet Explorer的非标准实现的attachEvent("onevent", callback),但这超出了弃用的范围,甚至不属于JavaScript(它属于一种叫做JScript的深奥语言)。尽可能避免使用多语言代码对您最有利。

helper类

为了解决困惑/抱怨,我写了一个“类”;执行此抽象(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();
}

这个类不会做所有事情,也不会处理所有可能的用例。我不喜欢去图书馆。但是对于一般的交互使用来说应该没问题。

要使用这个类,创建一个实例,并将其指向你想要关联键盘输入的元素:

var input_txt = Input(document.getElementById("txt"));


input_txt.watch("print_5", function(){
txt.value += "FIVE ";
}, "Control", "5");

这将做的是将一个新的输入侦听器附加到带有#txt的元素(让我们假设它是一个文本区域),并为组合键Ctrl+5设置一个观察点。当Ctrl5都关闭时,将调用传入的回调函数(在本例中,是将"FIVE "添加到文本区域的函数)。回调与名称print_5相关联,因此要删除它,只需使用:

input_txt.unwatch("print_5");

txt元素中分离input_txt:

input_txt.detach();

这样,垃圾收集可以拾取对象(input_txt),如果它被丢弃,你就不会有一个旧的僵尸事件侦听器。

为了彻底起见,这里有一个类的API的快速参考,以C/Java风格呈现,以便您知道它们返回什么以及它们期望的参数。

Boolean  key_down (String key);

如果key为down则返回true,否则返回false。

Boolean  keys_down (String key1, String key2, ...);

如果所有键key1 .. keyN都为down则返回true,否则返回false。

void     watch (String name, Function callback, String key1, String key2, ...);

创建一个“观察点”;这样按下所有keyN将触发回调

void     unwatch (String name);

通过其名称删除所述观察点

void     clear (void);

擦拭“键”;缓存。等价于上面的map = {}

void     detach (void);

从父元素中分离ev_kdownev_kup侦听器,使安全地摆脱实例成为可能

更新2017-12-02为了响应将此发布到github的请求,我创建了一个要点

更新2018-07-21我已经使用声明式编程有一段时间了,现在这种方式是我个人最喜欢的:小提琴pastebin

一般来说,它会在你实际需要的情况下工作(ctrl, alt, shift),但如果你需要同时点击,比如说,a+w,“;combine"将方法转换为多键查找。


我希望这个彻底解释的答案迷你博客对你有帮助:)

我使用这种方式(必须检查按下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;
}
});
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甚至调用多个函数,每个函数检查特定的键并适当地响应。

document.keydown = function (key) {


checkKey("x");
checkKey("y");
};

谁需要完整的示例代码。左+右补充道

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);

如果其中一个按键是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()
}
}
}
    $(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;
}
});


});

这不是一个通用的方法,但在某些情况下是有用的。它对于CTRL + 某物转变 + 某物CTRL + 转变 + 某物等组合非常有用。

示例:当你想使用CTRL + P打印一个页面时,第一个按下的键总是CTRL,然后是P。同样的CTRL + 年代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;


}
}


});

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);

我喜欢使用这个片段,它对于编写游戏输入脚本非常有用

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
}
}

只是让某些东西更稳定:

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;
}
});

如果你想找到任何按键事件与控制键,你可以这样做

onkeypress = (e) =>{
console.log(e);
if(e.ctrlKey && e.code == "KeyZ"){
document.write("do somthing")
} }

下面是布莱登 answer的实现。

var keys = {}
function handleKeyPress(evt) {
let { keyCode, type } = evt || Event; // to deal with IE
let isKeyDown = (type == 'keydown');
keys[keyCode] = isKeyDown;


// test: enter key is pressed down & shift isn't currently being pressed down
if(isKeyDown && keys[13] && !keys[16]){
console.log('user pressed enter without shift')
}
};


window.addEventListener("keyup", handleKeyPress);
window.addEventListener("keydown", handleKeyPress);

对于任何使用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>
);
}

请记住,我们需要在useEffect函数中使用backdropOpenLocal而不是backdropOpen,因为我们只想更新局部作用域变量并保持作用域的状态。

如果我们更新Example组件的状态并尝试访问backdropOpen,我们将得到与以前相同的值,除非我们在useEffect的依赖项数组中传入backdropOpen;这将导致useEffect内的作用域变量被重置,我们不希望这样。

如果有人需要简单的解决方案。

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 = [];
});