我正在尝试开发一个 JavaScript 游戏引擎,但遇到了这个问题:
问题是当我按右键然后按空格键时, Angular 色会跳跃然后停止移动。
我使用
keydown
函数来获得按下的键。如何检查是否同时按下了多个键?
请您参考如下方法:
注意:keyCode 现在是 deprecated.
如果您了解这个概念,则多次击键检测很容易
我的做法是这样的:
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
的值,其计算结果为真或假。现在都
map[65]
和
map[66]
设置为
true
.当你放开
A
,
keyup
事件触发,导致相同的逻辑为
map[65]
确定相反的结果(A),现在是假的,但因为
map[66]
(B) 仍然“down”(它没有触发 keyup 事件),它仍然是真的。
map
通过这两个事件,数组看起来像这样:
// keydown A
// keydown B
[
65:true,
66:true
]
// keyup A
// keydown B
[
65:false,
66:true
]
您现在可以做两件事:
A) 可以创建一个按键记录器 ( example ) 作为引用,供以后您想快速找出一个或多个按键代码时使用。假设您已经定义了一个 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 正文之后。 优化提示 : 大多数大牌网站都放了脚本标签 后 用于优化的 body 标签。这是因为脚本标记阻止加载更多元素,直到其脚本完成下载。将它放在内容之前允许内容提前加载。
B(这是您感兴趣的地方)您可以一次检查一个或多个 key ,其中
/*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
就这么简单:)
笔记
跟踪 key 代码
作为一般规则,记录代码是一种很好的做法,尤其是诸如 Key 代码(如
// CTRL+ENTER
)之类的东西,这样您就可以记住它们是什么。
您还应该按照与文档相同的顺序放置 key 代码(
CTRL+ENTER => map[17] && map[13]
,而不是
map[13] && map[17]
)。这样,当您需要返回并编辑代码时,您将永远不会感到困惑。
if-else 链的陷阱
如果检查不同数量的组合(如 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),您可能还想添加
return false
之后
map = []
,因此当“复制文件”功能被放置在 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()
event.keyCode
已弃用
用户 SeanVieira在评论中指出
event.keyCode
已弃用。
在那里,他给出了一个很好的选择:
event.key
,它返回被按下的键的字符串表示,如
"a"
对于 A,或
"Shift"
换档。
我继续做了一个 tool用于检查所述字符串。
element.onevent
对比
element.addEventListener
在
addEventListener
注册的处理程序可以堆叠,按注册顺序调用,同时设置
.onevent
direct 是相当激进的,并且会覆盖您之前拥有的任何内容。
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
注册。似乎更容易编写和推理。
还有
attachEvent("onevent", callback)
来自 Internet Explorer 的非标准实现,但这已经过时了,甚至不属于 JavaScript(它属于一种称为 JScript 的深奥语言)。尽可能避免多语言代码符合您的最大利益。
辅助类
为了解决混淆/投诉,我编写了一个“类”来进行这种抽象( pastebin link ):
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);
Returns
true
ifkey
is down, false otherwise.Boolean keys_down (String key1, String key2, ...);
Returns
true
if all keyskey1 .. keyN
are down, false otherwise.void watch (String name, Function callback, String key1, String key2, ...);
Creates a "watchpoint" such that pressing all of
keyN
will trigger the callbackvoid unwatch (String name);
Removes said watchpoint via its name
void clear (void);
Wipes the "keys down" cache. Equivalent to
map = {}
abovevoid detach (void);
Detaches the
ev_kdown
andev_kup
listeners from the parent element, making it possible to safely get rid of the instance
更新 2017-12-02 为了响应将其发布到 github 的请求,我创建了一个 gist .
更新 2018-07-21 我已经玩了一段时间的声明式编程,这种方式现在是我个人的最爱: fiddle , pastebin
通常,它适用于您实际想要的情况(ctrl、alt、shift),但如果您需要点击,例如
a+w
同时,将这些方法“组合”成多键查找也不会太困难。
我希望这个彻底解释的答案迷你博客有帮助:)