监听多个键盘按键的按下事件

8

我正在尝试让用户使用箭头键在页面上移动一个元素。目前为止,我已经实现了上/下/左/右的移动,但是对于斜线方向(同时按下两个箭头键)还没有实现。

我的监听器代码如下:

addEventListener('keydown', function(e){
    move = false;
    x = false;
    y = false;
    var keycode;
    if (window.event) keycode = window.event.keyCode;
    else if (e) keycode = e.which;
    switch(keycode){
        case 37:
            move = true;
            x = 'negative';
            //prevent page scroll
            e.preventDefault()
        break;
        case 38:
            move = true;
            y = 'negative'
            //prevent page scroll
            e.preventDefault()
        break;
        case 39:
            move = true;
            x = 'positive'
            //prevent page scroll
            e.preventDefault()
        break;
        case 40:
            move = true;
            y = 'positive'
            //prevent page scroll
            e.preventDefault()
        break;
    }
    if(move){
        animation.move(x,y);
    }
    return false;
})

如果用户按下箭头键,那么它会将x和y设置为负数或正数,并触发move()函数,该函数将以预设的像素数朝着所需的方向移动元素。如果同时按下两个键,则会触发第二个事件...我还希望用户能够通过快速释放和按下按键来无缝地改变方向。 然而,如果用户按下另一个方向键,似乎需要等待一段时间才能移动,除非他们完全释放该键然后再按下另一个键,否则不会对第二个键做出反应,直到第一个键被释放为止。
3个回答

9

Fiddle: http://jsfiddle.net/ATUEx/

创建一个临时缓存器来记录您的按键。

处理两个按键的实现将遵循以下模式:

  1. <keydown>
    • 清除之前的超时。
    • 检查是否已缓存某个按键代码。
      如果是,则执行以下操作:
      - 删除所有缓存的按键代码
      - 对该组合执行功能
      否则
      - 删除所有缓存的按键代码
      - 存储新的按键代码
      - 设置适当延迟的超时以清除按键代码(见下文)
  2. 重复1

适当的延迟:根据实际情况进行试验,找出足够的超时时间。当延迟时间太短时,下一个按键事件将无法找到先前输入的按键代码。

当延迟时间过长时,您不希望按键会堆积。


代码

我已经根据您的代码创建了一个高效的函数,您应该可以很容易地实现它。

(function(){ //Anonymous function, no leaks
    /* Change the next variable if necessary */
    var timeout = 200; /* Timeout in milliseconds*/

    var lastKeyCode = -1;
    var timer = null;
    function keyCheck(ev){
        var keyCode = typeof ev.which != "undefined" ? ev.which : event.keyCode;
        /* An alternative way to check keyCodes:
         * if(keyCode >= 37 && keyCode <= 40) ..*/
         /*37=Left  38=Up  39=Right  40=Down */
        if([37, 38, 39, 40].indexOf(keyCode) != -1){

            /* lastKeyCode == -1 = no saved key
               Difference betwene keyCodes == opposite keys = no possible combi*/
            if(lastKeyCode == -1 || Math.abs(lastKeyCode - keyCode) == 2){
                refresh();
                lastKeyCode = keyCode;
            } else if(lastKeyCode == keyCode){
                clear([lastKeyCode]);
            } else {
                /* lastKeyCode != -1 && keyCode != lastKeyCode
                   and no opposite key = possible combi*/
                clear([lastKeyCode, keyCode]);
                lastKeyCode = -1
            }
            ev.preventDefault(); //Stop default behaviour
            ev.stopPropagation(); //Other event listeners won't get the event
        }

        /* Functions used above, grouped together for code readability */
        function reset(){
            keyCombi([lastKeyCode]);
            lastKeyCode = -1;
        }
        function clear(array_keys){
            clearTimeout(timer);
            keyCombi(array_keys);
        }
        function refresh(){
            clearTimeout(timer);
            timer = setTimeout(reset, timeout);
        }
    }

    var lastX = false;
    var lastY = false;
    function keyCombi(/*Array*/ keys){
        /* Are the following keyCodes in array "keys"?*/
        var left = keys.indexOf(37) != -1;
        var up = keys.indexOf(38) != -1;
        var right = keys.indexOf(39) != -1;
        var down = keys.indexOf(40) != -1;

        /* What direction? */
        var x = left ? "negative" : right ? "positive" : false;
        var y = up ? "negative" : down ? "positive" : false;
        /* Are we heading to a different direction?*/
        if(lastX != x || lastY != y) animation.move(x, y);
        lastX = x;
        lastY = y;
    }

    //Add event listener
    var eventType = "keydown";window["on"+eventType] = keyCheck;
})();

在匿名函数的末尾,添加了keydown事件监听器。此事件仅在按下键时触发。当第二个键按得足够快时,代码将识别两个连续的键击,并调用keyCombi()
我设计了keyCombi智能化,只有在值改变时才调用animation.move(x,y)。此外,我实现了同时处理两个方向的可能性。
请注意:我将函数包含在匿名函数包装器中,以便变量不会在全局(window)范围内定义。如果您不关心作用域,请随意删除第一行和最后一行。

我很好奇你说我应该删除缓存的键码。我认为我主要想在“keyup”事件上这样做?或者说,按键按下事件会一直触发直到按键被松开? - Chris Sobolewski
@ChrisSobolewski 我已经更新了我的答案。同时,请查看Fiddle链接以获取实时示例。 - Rob W
哇,谢谢!这与我的想法非常相似...您绝对做得比我预期的更好,先生。 - Chris Sobolewski

2
这里是稍加修改后的代码,供您参考... http://jsfiddle.net/w8uNz/ 它完美地完成了工作,可将元素移动到其父元素中。
Rob W说得没错,您应该在一段时间内动态缓存按下的键。但是为什么要这样做呢?如果您正在制作动态游戏,则必须有更高的抽象级别。总的来说,如果要进行此类操作,您需要提高自己的编码水平。(否则它只会给您带来头疼。)

1

从逻辑上讲,这听起来相当简单:

  1. 第一个键触发按键事件,将此事件存储在堆栈中
  2. 然后第二个键触发按键事件,将此事件存储在堆栈中
  3. 现在你有两个按下的键,没有松开的键
  4. 如果松开键,则有三种可能性
    1. 只有一个事件 => 沿着那个方向走
    2. 有两个事件 => 斜着走
    3. 有两个以上的事件或无效选项(例如右和左)=> 什么也不做,或者你想做什么就做什么
  5. 清除堆栈

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接