首页 文章

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

提问于
浏览
136

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

  • 当我按空格键时,角色会跳跃 .

  • 当我按→字符向右移动时 .

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

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

9 回答

  • 0

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

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

    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)的相反结果,现在为假,但是因为 map[66] (B)仍然是"down"(它没有触发键盘事件),它仍然是真的 .

    通过这两个事件, map 数组如下所示:

    // keydown A 
    // keydown B
    [
        65:true,
        66:true
    ]
    // keyup A
    // keydown B
    [
        65:false,
        66:true
    ]
    

    你现在可以做两件事:

    A) 当您想要快速找出一个或多个键码时,可以创建键 Logger (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元素,可以使用 element 在javascript中轻松引用

    alert(element); // [Object HTMLDivElement]
    

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

    只需确保脚本标记位于HTML正文之后 . Optimization tip :大多数大牌网站都将脚本标记 after 作为优化的正文标记 . 这是因为脚本标记会阻止其他元素加载,直到脚本完成下载 . 将其置于内容之前允许预先加载内容 .

    B (which is where your interest lies) 您可以在 /*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');
    }
    

    Edit :这不是重要的,所以你可以尝试这样的东西让眼睛更容易:

    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

    就像那一样简单:)

    注意事项

    跟踪KeyCodes

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

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

    与if-else链相关的问题

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

    // 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
    

    Gotcha:浏览器默认值

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

    问题:由于浏览器通常对键组合有默认操作(如CtrlD激活书签窗口,或者CtrlShiftC激活maxthon上的skynote),您可能还想在 map = [] 之后添加 return false ,因此当您的网站用户不会感到沮丧时"Duplicate File"函数,放在CtrlD上,代替书签 .

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

    如果没有 return false ,书签窗口会弹出,让用户感到沮丧 .

    返回声明(新)

    好的,所以你不要't always want to exit the function at that point. That'为什么 event.preventDefault() 功能在那里 . 它的作用是设置一个告诉内部标志的内部标志解释器到 not 允许浏览器运行其默认操作 . 之后,继续执行该功能(而 return 将立即退出该功能) .

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

    不推荐使用

    event.keyCode

    用户SeanVieira在评论中指出 event.keyCode 已被弃用 .

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

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

    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 's non-standard implementation, but this is beyond deprecated and doesn'中也有 attachEvent("onevent", callback) 甚至属于JavaScript(它属于一种叫做JScript的深奥语言) . 尽可能避免使用多语言代码符合您的最佳利益 .

    辅助类

    为了解决混淆/抱怨,我写了一个"class"来做这个抽象(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 (让's assume it' s为textarea)将新的输入侦听器附加到元素,并为键组合 Ctrl+5 设置一个观察点 . 当 Ctrl5 都关闭时,将传入您传入的回调函数(在本例中,是一个将 "FIVE " 添加到textarea的函数) . 回调与名称 print_5 相关联,因此要删除它,您只需使用:

    input_txt.unwatch("print_5");
    

    要从 txt 元素中分离 input_txt

    input_txt.detach();
    

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

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

    Boolean key_down(String key);
    如果key为down,则返回true,否则返回false . Boolean keys_down(String key1,String key2,...);
    如果所有键key1 .. keyN都关闭则返回true,否则返回false . void watch(String name,Function callback,String key1,String key2,...);
    创建一个“观察点”,按下所有keyN将触发回调void unwatch(String name);
    通过名称void clear(void)删除所述观察点;
    擦除“按键”缓存 . 相当于map = {}高于void detach(void);
    将ev_kdown和ev_kup侦听器与父元素分离,从而可以安全地删除实例

    Update 2017-12-02 为了响应将此发布到github的请求,我创建了一个gist .

    Update 2018-07-21 我已经玩了一段时间的声明式编程,这种方式现在是我个人的最爱:fiddlepastebin

    一般来说,它会适用于你想要的情况(ctrl,alt,shift),但是如果你需要同时点击 a+w ,那么进入多方的方法就不会太难了 . -key的查找 .


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

  • 248

    您应该使用 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时,这个概念也同样适用 .

  • 6
    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");
    
        } 
    
    }
    
  • 7

    我用这种方式(必须检查哪里是按住Shift键):

    // 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;
        }
    });
    
  • 0

    使keydown甚至调用多个函数,每个函数检查一个特定的键并适当地响应 .

    document.keydown = function (key) {
    
        checkKey("x");
        checkKey("y");
    };
    
  • 28

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

    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);
    
  • 2

    如果按下其中一个键是Alt / Crtl / Shift,您可以使用此方法:

    document.body.addEventListener('keydown', keysDown(actions) );
    
    function actions() {
       // do stuff here
    }
    
    // simultaneous pressing Alt + R
    function keysDown (cb) {
      return function (zEvent) {
        if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
          return cb()
        }
      }
    }
    
  • 1

    我试着在 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) ;
    }
    

    这只是为了说明一种模式;我不会在这里详细介绍(尤其不是浏览器特定的level2 Event 注册) .

    请回复一下这是否有帮助 .

  • 1
    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;
    

    我知道,这不是最好的方式 .

相关问题