一次按下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一个数组以跟踪多个键。 然后可以使用数组一次检查一个或多个键。
为了解释,假设你按下A和B ,每个都会触发一个keydown
事件,将map[e.keyCode]
设置为e.type == keydown
的值,其值为true或false 。 现在map[65]
和map[66]
都被设置为true
。 当你放开A
, keyup
事件触发,导致同样的逻辑确定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 A , Ctrl Shift B和Ctrl Shift C
就这么简单:)
笔记
保持键码的跟踪
作为一个通用规则,文档代码是一个很好的习惯,尤其是像键盘代码(比如// CTRL+ENTER
),所以你可以记住它们是什么。
您还应该按照与文档( CTRL+ENTER => map[17] && map[13]
,NOT 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"
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 false
或e.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
设置一个观察点。 当Ctrl
和5
都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
将触发callbackvoid unwatch (String name);
通过其名称移除所述观察点
void clear (void);
擦除“关键”caching。 等同于上面的
map = {}
void detach (void);
将
ev_kdown
和ev_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);