Skip to main content
 首页 » 编程设计

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

2024年07月26日40lovecherry

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

  • 当我按 SPACE 时, Angular 色会跳跃。
  • 当我按 → 字符向右移动时。

  • 问题是当我按右键然后按空格键时, 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.addEventListeneraddEventListener 注册的处理程序可以堆叠,按注册顺序调用,同时设置 .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 .当两者 Ctrl5关闭时,您传入的回调函数(在本例中为将 "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 if key is down, false otherwise.

    Boolean  keys_down (String key1, String key2, ...); 
    

    Returns true if all keys key1 .. 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 callback

    void     unwatch (String name); 
    

    Removes said watchpoint via its name

    void     clear (void); 
    

    Wipes the "keys down" cache. Equivalent to map = {} above

    void     detach (void); 
    

    Detaches the ev_kdown and ev_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同时,将这些方法“组合”成多键查找也不会太困难。

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