我正在尝试开发一个JavaScript游戏引擎,我遇到了这个问题:
问题是当我按右键然后按空格时,角色会跳跃然后停止移动。
我使用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 */
}
这段代码非常简单:由于计算机每次只通过一次击键,因此创建了一个数组来保持对多个键的跟踪。 然后可以使用该数组一次检查一个或多个键。
为了解释,假设您按下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事件),因此它仍然为true。
通过这两个事件,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的$()
。
只需确保脚本标记位于HTML正文之后。 优化提示:大多数大牌网站都将脚本标签放在正文标签之后进行优化。 这是因为脚本标记阻止加载更多的元素,直到其脚本完成下载。 将它放在内容之前允许预先加载内容。
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');
}
}
(编辑结束)
此示例检查CtrlShiftA,CtrlShiftB和CtrlShiftC
就是这么简单:)
作为一般规则,记录代码是很好的做法,特别是像键代码(如//ctrl+enter
)这样的事情,以便您能够记住它们是什么。
您还应该按照与文档相同的顺序放置关键代码(ctrl+enter=>map[17]&map[13]
,而不是map[13]&map[17]
)。 这样,当您需要回去编辑代码时,您就不会感到困惑了。
如果检查不同数量的组合(如CtrlShiftAltEnter和CtrlEnter),请将较小的组合放在较大的组合之后,否则较小的组合将覆盖较大的组合(如果它们足够相似)。 示例:
// 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激活书签窗口,或者CtrlShiftC激活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
,书签窗口将弹出,令用户沮丧。
好的,所以你并不总是想在那点退出函数。 这就是event.preventDefault()
函数的原因。 它所做的是设置一个内部标志,告诉解释器不允许浏览器运行其默认操作。 之后,函数继续执行(而return
将立即退出函数)。
在您决定是使用return false
还是使用e.preventdefault()
之前,先了解这种区别
用户SeanVieira在评论中指出,不推荐使用event.keycode
。
在这里,他给出了一个很好的替代方案:event.key
,它返回所按键的字符串表示形式,例如a的“a”
,或者shift的“shift”
。
我继续做了一个工具来检查这些字符串。
使用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();
}
这个类不是做所有的事情,它不会处理每一个可以想象的用例。 我不是图书馆的人。 但是对于一般的交互使用来说,它应该是很好的。
若要使用此类,请创建一个实例,并将其指向要将键盘输入与之关联的元素:
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的函数)。 回调与名称print_5
相关联,因此要删除它,只需使用:
input_txt.unwatch("print_5");
要将input_txt
与txt
元素分离,请执行以下操作:
input_txt.detach();
通过这种方式,垃圾收集可以捡起对象(input_txt
),如果它被扔掉,那么您就不会有一个旧的僵尸事件侦听器遗留下来。
为了彻底起见,这里有一个对类API的快速引用,以C/Java风格呈现,这样您就知道它们返回了什么以及它们期望的参数。
Boolean key_down (String key);
如果key
关闭,则返回true
,否则返回false。
Boolean keys_down (String key1, String key2, ...);
返回true
,如果所有键key1.. keyn
为down,否则为false。
void watch (String name, Function callback, String key1, String key2, ...);
创建“观察点”,以便按下所有键n
将触发回调
void unwatch (String name);
通过其名称移除所述观察点
void clear (void);
清除“Keys Down”缓存。 相当于上面的map={}
void detach (void);
将ev_kdown
和ev_kup
侦听器与父元素分离,从而可以安全地摆脱实例
更新2017-12-02作为对将此发布到github的请求的响应,我创建了一个Gist。
更新2018-07-21我玩声明式编程已经有一段时间了,现在我个人最喜欢这样的方式:fiddle,pastebin
通常,它可以用于您实际需要的情况(ctrl,alt,shift),但是如果您需要同时点击a+w
,那么将这些方法“组合”成一个多键查找并不困难。
我希望这个解释透彻的答案小博客对我有帮助:)
您应该使用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时,这个概念也同样适用。
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");
}
}