我正在尝试开发一个 JavaScript 游戏引擎,但遇到了这个问题:
SPACE
→
问题是当我按右然后按空格时,角色会跳跃然后停止移动。
我使用该keydown功能来按下按键。如何检查是否同时按下了多个键?
keydown
注意: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 */ }
这段代码非常简单:由于计算机一次只敲击一个键,因此创建了一个数组来跟踪多个键。然后可以使用该数组一次检查一个或多个键。
只是为了解释一下,假设您按下Aand B,每个都会触发一个设置为 值的keydown事件,该值的计算结果为 true 或 false 。现在两者和都设置为。当您松开 时,事件触发,导致相同的逻辑确定(A) 的相反结果,现在为 false ,但由于(B) 仍然“向下”(它没有触发 keyup 事件),它仍然是 真实的 。map[e.keyCode]``e.type == keydown map[65]``map[66]``true``A``keyup``map[65] map[66]
A
B
map[e.keyCode]``e.type == keydown
map[65]``map[66]``true``A``keyup``map[65]
map[66]
通过这两个事件的map数组如下所示:
map
// keydown A // keydown B [ 65:true, 66:true ] // keyup A // keydown B [ 65:false, 66:true ]
你现在可以做两件事:
A) 当您想快速找出一个或多个键码时,可以创建一个键记录器(示例)作为参考。假设您已经定义了一个 html 元素并使用变量element.
element
element.innerHTML = ''; var i, l = map.length; for(i = 0; i < l; i ++){ if(map[i]){ element.innerHTML += '<hr>' + i; } }
id注意:您可以通过其属性轻松获取元素。
id
<div id="element"></div>
这将创建一个可以在 javascript 中轻松引用的 html 元素element
alert(element); // [Object HTMLDivElement]
您甚至不必使用document.getElementById()或$()抓住它。但为了兼容性,$()更广泛推荐使用 jQuery。
document.getElementById()
$()
只需确保 脚本 标记位于 HTML 正文之后。 优化提示 :大部分大牌网站都将script标签放在body标签之后进行优化 。 这是因为脚本标签会阻止加载更多元素,直到其脚本完成下载。将其放在内容之前可以预先加载内容。
B(这是您感兴趣的地方) 您可以一次检查一个或多个密钥,/*insert conditional here*/例如:
/*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``Shift``A
Ctrl``Shift``B
Ctrl``Shift``C
就这么简单:)
作为一般规则,记录代码是一种很好的做法,尤其是像键代码(如// CTRL+ENTER)这样的东西,这样您就可以记住它们是什么。
// CTRL+ENTER
您还应该按照与文档相同的顺序放置密钥代码(CTRL+ENTER => map[17] && map[13], NOT map[13] && map[17])。这样,当您需要返回并编辑代码时,您就不会感到困惑。
CTRL+ENTER => map[17] && map[13]
map[13] && map[17]
如果检查不同数量的组合(如Ctrl``Shift``Alt``Enter和Ctrl``Enter),请将较小的组合放在较大的组合 之后 ,否则如果它们足够相似,较小的组合将覆盖较大的组合。例子:
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’ 事件不触发。例如:
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
这是我发现的一件烦人的事情,解决方案包括:
问题:由于浏览器通常对按键组合具有默认操作(例如Ctrl``D激活书签窗口,或Ctrl``Shift``C激活 maxthon 上的 skynote),您可能还想添加return falseafter map = [],这样您网站的用户就不会在“重复文件”时感到沮丧功能,被放置Ctrl``D,而不是书签页面。
Ctrl``D
return false
if(map[17] && map[68]){ // CTRL+D alert('The bookmark window didn\'t pop up!'); map = {}; return false; }
如果没有return false,书签窗口 会 弹出,让用户感到沮丧。
好的,所以您并不总是想在那时退出该功能。这就是event.preventDefault()功能存在的原因。它所做的是设置一个内部标志,告诉解释器 不允许 浏览器运行其默认操作。之后,函数的执行继续(而return将立即退出函数)。
event.preventDefault()
return
在决定是否使用return false或e.preventDefault()
e.preventDefault()
event.keyCode
用户SeanVieiraevent.keyCode在已弃用的评论中指出。
在那里,他给出了一个很好的替代方案:event.key,它返回一个表示被按下的键的字符串,例如"a"forA或"Shift"for Shift。
event.key
"a"
"Shift"
Shift
我继续制作了一个用于检查所述字符串的工具。
element.onevent
element.addEventListener
注册的处理程序addEventListener可以堆叠,并按注册的顺序调用,而.onevent直接设置是相当激进的,并且会覆盖您以前拥有的任何内容。
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属性似乎覆盖了所有内容,并且 and 的行为ev.preventDefault()可能return false;相当不可预测。
ev.preventDefault()
return false;
在任何一种情况下,通过注册的处理程序addEventlistener似乎更容易编写和推理。
addEventlistener
也有attachEvent("onevent", callback)来自 Internet Explorer 的非标准实现,但它已被弃用,甚至与 JavaScript 无关(它与称为 JScript 的深奥语言有关)。尽可能避免使用多语言代码符合您的最大利益。
attachEvent("onevent", callback)
为了解决混淆/投诉,我编写了一个执行此抽象的“类”(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。当两者Ctrl都5关闭时,将调用您传入的回调函数(在本例中是添加"FIVE "到 textarea 的函数)。回调与 name 相关联print_5,因此要删除它,您只需使用:
#txt
Ctrl+5
Ctrl
5
"FIVE "
print_5
input_txt.unwatch("print_5");
input_txt要从txt元素中分离:
input_txt
txt
input_txt.detach();
这样,垃圾收集可以拾取对象 ( input_txt),如果它被丢弃,并且您不会留下旧的僵尸事件侦听器。
为彻底起见,这里是对类 API 的快速参考,以 C/Java 风格呈现,以便您了解它们返回的内容和期望的参数。
Boolean key_down (String key); true如果关闭则返回key,否则返回 false。 Boolean keys_down (String key1, String key2, ...); true如果所有键都关闭,则返回key1 .. keyN,否则返回 false。 void watch (String name, Function callback, String key1, String key2, …); 创建一个“观察点”,这样按下所有keyN将触发回调 void unwatch (String name); 通过其名称删除所述观察点 void clear (void); 擦除“keys down”缓存。相当于map = {}上面 void detach (void); 从父元素中分离ev_kdown和ev_kup侦听器,从而可以安全地摆脱实例
Boolean key_down (String key);
true如果关闭则返回key,否则返回 false。
true
key
Boolean keys_down (String key1, String key2, ...);
true如果所有键都关闭,则返回key1 .. keyN,否则返回 false。
key1 .. keyN
void watch (String name, Function callback, String key1, String
key2, …);
创建一个“观察点”,这样按下所有keyN将触发回调
keyN
void unwatch (String name);
通过其名称删除所述观察点
void clear (void);
擦除“keys down”缓存。相当于map = {}上面
map = {}
void detach (void);
从父元素中分离ev_kdown和ev_kup侦听器,从而可以安全地摆脱实例
ev_kdown
ev_kup
更新 2017-12-02 为了响应将其发布到 github 的请求,我创建了一个gist。
更新 2018-07-21 我一直在玩声明式编程,现在这种方式是我个人最喜欢的方式:fiddle,pastebin
一般来说,它适用于你真正想要的情况(ctrl、alt、shift),但如果你需要同时点击,a+w那么将这些方法“组合”成一个多键查找。
a+w
我希望这个 详尽解释的答案 迷你博客对您有所帮助:)