一次按下JavaScript多个按键

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

当我按空格字符跳转。 当我按下右箭头时,angular色向右移动。

事情是,当我继续按右键然后我按空格字符跳,然后停止移动。

我使用keydown函数来获取按键,如何检查是否有多个按键被按下?

如果你理解这个概念,多键击检测是很容易的

我这样做的方式是这样的:

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

此代码非常简单:由于计算机一次仅传递一个按键,因此会创build一个数组以跟踪多个键。 然后可以使用数组一次检查一个或多个键。

为了解释,假设你按下AB ,每个都会触发一个keydown事件,将map[e.keyCode]设置为e.type == keydown的值,其值为truefalse 。 现在map[65]map[66]都被设置为true 。 当你放开Akeyup事件触发,导致同样的逻辑确定map[65] (A)的相反结果,现在它是false ,但是因为map[66] (B)仍然是“down”(它没有触发一个keyup事件),它仍然是真实的

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

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

你现在可以做两件事情:

A)当你想快速找出一个或多个关键代码时,可以创build一个关键logging器( 例子 )作为参考。 假设你已经定义了一个html元素并用variableselement指向它。

 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> 

这创build了一个html元素,可以在元素中轻松地引用javascript

 alert(element); // [Object HTMLDivElement] 

您甚至不必使用document.getElementById()$()来获取它。 但为了兼容性,更广泛地推荐使用jQuery的$()

只要确保script标签在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'); } } 

(编辑结束)


此示例检查Ctrl Shift ACtrl Shift BCtrl Shift C

就这么简单:)

笔记

保持键码的跟踪

作为一个通用规则,文档代码是一个很好的习惯,尤其是像键盘代码(比如// CTRL+ENTER ),所以你可以记住它们是什么。

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

if-else链是个问题

如果检查不同数量的组合(如Ctrl Shift Alt EnterCtrl 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" 

Got::“即使我没有按键,这个组合键仍然保持激活状态”

处理警报或任何需要从主窗口获取焦点的内容时,可能需要在条件完成后包含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 

陷阱:浏览器默认值

这是一个令人讨厌的事情,我发现,解决scheme包括:

问题:由于浏览器通常对键组合有默认动作(如Ctrl D激活书签窗口,或Ctrl Shift C激活maxthon上的skynote),因此您可能还希望在map = []后添加return false ,以便您的站点当放置在Ctrl D上的“重复文件”function将书签页面时,不会感到沮丧。

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

没有return false ,书签窗口popup,令用户感到沮丧。

返回声明(新)

好的,所以你并不总是想在这个时候退出这个function。 这就是为什么event.preventDefault()函数在那里。 它所做的是设置一个内部标志,告诉解释器不允许浏览器运行其默认操作。 之后,继续执行该函数(而return将立即退出函数)。

在决定是否使用return falsee.preventDefault()之前理解这个区别

event.keyCode已被弃用

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

在那里,他给出了一个很好的select: event.key ,它返回被按下的键的string表示forms,比如"a" A "a"表示A"Shift"表示Shift

我继续制作一个工具来检查string。

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的深奥语言)。 尽可能避免使用多边制代码是符合您的最佳利益的。

助手类

为了解决混淆/投诉,我写了一个“类”,这个抽象( 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(); } 

这个类没有做任何事情,也不会处理所有可能的用例。 我不是一个图书馆的人。 但对于一般的互动使用,应该没问题。

要使用此类,请创build一个实例并将其指向要将键盘input与之关联的元素:

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

这将做什么是附加一个新的input监听器与#txt的元素(让我们假设它是一个textarea),并为组合键Ctrl+5设置一个观察点。 当Ctrl5都closures时,你传入的callback函数(在这种情况下,一个将"FIVE "添加到textarea的函数)将被调用。 该callback与名称print_5关联,因此要删除它,只需使用:

 input_txt.unwatch("print_5"); 

txt元素中分离出input_txt

 input_txt.detach(); 

这样,垃圾收集可以拿起对象( input_txt ),如果它被扔掉,你将不会有一个老的僵尸事件监听器遗留下来。

为了彻底,下面是以C / Java风格呈现的类的API的快速引用,以便您知道它们返回的内容以及期望的参数。

 Boolean key_down (String key); 

如果closures,则返回true ,否则返回false。

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

如果所有密钥key1 .. keyN都closures,则返回true ,否则返回false。

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

创build一个“观察点”,按下所有keyN将触发callback

 void unwatch (String name); 

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

 void clear (void); 

擦除“关键”caching。 等同于上面的map = {}

 void detach (void); 

ev_kdownev_kup监听器从父元素中分离出来,从而可以安全地删除实例


我希望这个彻底解释的答案是有帮助的:)

您应该使用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时,这个概念也同样适用。

我用这种方法(必须检查按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"); } } 

甚至可以调用多个函数,每个函数检查一个特定的键并作出适当的响应。

 document.keydown = function (key) { checkKey("x"); checkKey("y"); }; 

我想尝试在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) ; } 

这只是为了说明一个模式; 我不会在这里详细介绍(特别是浏览器的具体级别2 + Event注册)。

请回发请这是否有帮助。

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