如何检测是否使用JavaScript一次按下多个键?

Cri*_*sty 158 javascript keydown

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

  • 当我按下SPACE角色跳跃.
  • 当我按下角色向右移动时.

问题是,当我按下右键然后按空格键时,角色跳跃然后停止移动.

我使用该keydown功能来按下键.如何检查是否一次按下多个键?

Bra*_*est 301

如果您了解这个概念,就可以轻松进行多次击键检测

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

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 */
}
Run Code Online (Sandbox Code Playgroud)

此代码非常简单:由于计算机一次只传递一次击键,因此会创建一个数组来跟踪多个键.然后可以使用该阵列一次检查一个或多个密钥.

只是为了说明,假设你按AB,每触发一个keydown,设置事件map[e.keyCode],它的值e.type == keydown,其计算结果为.现在,这两个map[65]map[66]设置为true.当你松开时A,keyup事件触发,导致相同的逻辑确定map[65](A)的相反结果,现在是假的,但是因为map[66](B)仍然"向下"(它没有触发键盘事件),它仍然是真的.

map阵列,通过这两个事件,看起来是这样的:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]
Run Code Online (Sandbox Code Playgroud)

你现在可以做两件事:

A)当您想要快速找出一个或多个密钥代码时,可以创建密钥记录器(示例)作为参考.假设您已经定义了一个html元素并使用该变量指向它element.

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:您可以通过其id属性轻松获取元素.

<div id="element"></div>
Run Code Online (Sandbox Code Playgroud)

这将创建一个可以在javascript中轻松引用的html元素 element

alert(element); // [Object HTMLDivElement]
Run Code Online (Sandbox Code Playgroud)

你甚至不必使用document.getElementById()$()抓住它.但是为了兼容性,$()更广泛地推荐使用jQuery .

只需确保脚本标记位于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');
}
Run Code Online (Sandbox Code Playgroud)

编辑:这不是最易读的代码段.可读性很重要,所以你可以试试这样的东西让眼睛更容易:

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;
}
Run Code Online (Sandbox Code Playgroud)

用法:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')
Run Code Online (Sandbox Code Playgroud)

这是否更好?

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');
    }
}
Run Code Online (Sandbox Code Playgroud)

(编辑结束)


此示例检查CtrlShiftA,CtrlShiftBCtrlShiftC

就像那一样简单:)

笔记

跟踪KeyCodes

作为一般规则,最好记录代码,特别是密钥代码(如// CTRL+ENTER),以便您记住它们是什么.

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

与if-else链相关的问题

如果检查不同数量的组合(如CtrlShiftAltEnterCtrlEnter),请较大的组合放置较小的组合,否则较小的组合将覆盖较大的组合(如果它们足够相似).例:

// 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"
Run Code Online (Sandbox Code Playgroud)

问:"即使我没有按键,这个键组合仍然会激活"

处理警报或从主窗口获取焦点的任何事物时,您可能希望包括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
Run Code Online (Sandbox Code Playgroud)

问题:浏览器默认值

这是我发现的烦人的事情,包括以下解决方案:

问题:由于浏览器通常对键连击默认动作(如CtrlD激活书签窗口,或CtrlShiftC激活上傲游skynote),您可能还需要添加return falsemap = [],所以你的网站的用户不会感到沮丧时,"重复文件"功能,被戴上CtrlD,书签页面而不是.

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

没有return false,书签窗口弹出,令用户感到沮丧.

退货声明(新)

好的,所以你并不总是想在那时退出这个功能.这就是event.preventDefault()函数存在的原因.它的作用是设置一个内部标志,告诉解释,以使浏览器运行的默认操作.之后,继续执行该功能(而return将立即退出该功能).

在决定是否使用return false或之前,请先了解这一区别e.preventDefault()

event.keyCode 已弃用

用户SeanVieira在评论中指出了event.keyCode不推荐使用的内容.

在那里,他提供了一个很好的选择:event.key它返回被按下的键的字符串表示,如"a"for A"Shift"for Shift.

我继续做了一个工具来检查所说的琴弦.

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
});
Run Code Online (Sandbox Code Playgroud)

.onevent属性似乎覆盖了一切和行为,ev.preventDefault()并且return false;可能是相当不可预测的.

在任何一种情况下,通过注册的处理程序addEventlistener似乎更容易编写和推理.

还有attachEvent("onevent", callback)来自Internet Explorer的非标准实现,但这已经被弃用了,甚至不属于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();
}
Run Code Online (Sandbox Code Playgroud)

这个类不会做任何事情,它不会处理每个可能的用例.我不是图书馆的人.但对于一般的交互式使用它应该没问题.

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

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");
Run Code Online (Sandbox Code Playgroud)

这将做的是将一个新的输入监听器附加到元素#txt(让我们假设它是一个textarea),并为关键组合设置一个观察点Ctrl+5.当两个Ctrl5下降,你传递的回调函数(在这种情况下,增加了一个功能"FIVE ",以textarea的)将会被调用.回调与名称相关联print_5,因此要删除它,您只需使用:

input_txt.unwatch("print_5");
Run Code Online (Sandbox Code Playgroud)

要卸下input_txttxt元素:

input_txt.detach();
Run Code Online (Sandbox Code Playgroud)

这样,垃圾收集可以拾取object(input_txt),如果它被丢弃,你将不会留下旧的僵尸事件监听器.

为了彻底,这里是对类的API的快速参考,以C/Java风格呈现,以便您知道它们返回什么以及它们期望的参数.

Boolean  key_down (String key);
Run Code Online (Sandbox Code Playgroud)

true如果key为down则返回,否则返回false.

Boolean  keys_down (String key1, String key2, ...);
Run Code Online (Sandbox Code Playgroud)

true如果所有键key1 .. keyN都关闭则返回,否则返回false.

void     watch (String name, Function callback, String key1, String key2, ...);
Run Code Online (Sandbox Code Playgroud)

创建一个"观察点",按下所有按钮keyN将触发回调

void     unwatch (String name);
Run Code Online (Sandbox Code Playgroud)

通过名称删除所述观察点

void     clear (void);
Run Code Online (Sandbox Code Playgroud)

擦除"按键"缓存.相当于map = {}上述

void     detach (void);
Run Code Online (Sandbox Code Playgroud)

将父元素ev_kdownev_kup侦听器分离,从而可以安全地删除实例

更新2017-12-02为了回应将此发布到github的请求,我创建了一个要点.

更新2018-07-21我已经玩了一段时间的声明式编程,这种方式现在是我个人的最爱:小提琴,pastebin

一般来说,它会适用于你想要的情况(ctrl,alt,shift),但是如果你需要同时点击a+w,那么将这些方法"组合"成一个并不太困难.多键查找.


我希望这个彻底解释回答迷你博客很有帮助:)

  • 注意:不推荐使用`keyCode` - 如果切换到[`key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key),那么你将获得实际的字符表示形式可以很好的关键. (3认同)

Mar*_*ijn 30

您应该使用keydown事件来跟踪按下的键,并且您应该使用keyup事件来跟踪释放键的时间.

看到这个例子:http://jsfiddle.net/vor0nwe/mkHsU/

(更新:我在这里复制代码,以防jsfiddle.net保释:) HTML:

<ul id="log">
    <li>List of keys:</li>
</ul>
Run Code Online (Sandbox Code Playgroud)

...和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');
});
Run Code Online (Sandbox Code Playgroud)

在那个例子中,我正在使用一个数组来跟踪正在按下哪些键.在实际应用程序中,delete一旦释放了相关键,您可能希望使用每个元素.

请注意,虽然我在这个例子中使用了jQuery让自己变得简单,但在使用"原始"Javascript时,这个概念也同样适用.

  • @Cristy:那么你也可以添加一个`onblur`事件处理程序,它从数组中删除所有按下的键.一旦你失去焦点,就必须再次按下所有键.不幸的是,没有JS等同于`GetKeyboardState`. (3认同)
  • 看起来当您一次按下三个或更多按键时,它会停止检测任何按键,直到您抬起一个按键.(使用Firefox 22测试) (2认同)

Edu*_*nda 15

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

    } 

}
Run Code Online (Sandbox Code Playgroud)


Arr*_*ray 7

我用这种方式(必须检查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;
    }
});
Run Code Online (Sandbox Code Playgroud)


Rez*_*our 6

谁需要完整的示例代码。右+左添加

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);
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说是赢家。简洁,功能齐全。如果您需要知道您所按下的键的名称,只需在 keydown 函数末尾记录 keyPressed 对象即可。谢谢你,朋友! (2认同)

Jak*_*uda 5

这不是通用方法,但在某些情况下很有用。它对于CTRL+somethingShift+somethingCTRL+ Shift+something等组合很有用。

示例:当您想使用CTRL+打印页面时P,按下的第一个键总是CTRL后跟P。与CTRL+ SCTRL+U和其他组合相同。

document.addEventListener('keydown',function(e){
      
    //SHIFT + something
    if(e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('Shift + S');
                break;

        }
    }

    //CTRL + SHIFT + something
    if(e.ctrlKey && e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('CTRL + Shift + S');
                break;

        }
    }

});
Run Code Online (Sandbox Code Playgroud)