为啥 onkeyup 事件不会在 Javascript 游戏中触发

Posted

技术标签:

【中文标题】为啥 onkeyup 事件不会在 Javascript 游戏中触发【英文标题】:Why are onkeyup events not firing in Javascript game为什么 onkeyup 事件不会在 Javascript 游戏中触发 【发布时间】:2012-06-28 20:30:47 【问题描述】:

我有一个 2d javascript 游戏的开始 - 目前玩家可以使用箭头键在屏幕上驱动一个三角形。

问题是有时三角形会卡在一个方向旋转或向前移动,直到再次按下相应的控制键并再次触发onkeyup 事件。这通常发生在同时按下多个控制键时。

除非onkeyup 事件由于某种原因没有被触发,否则我无法弄清楚为什么它首先会被卡住。任何帮助将不胜感激,谢谢。

这里是一些代码,你可以找到fully working example on JSFiddle:

...

function init()
    var canvas = document.getElementById('canvas');
    if(canvas.getContext)
        setInterval(play, 50);
    


function play()
    printTriState();
    updateTriAcceleration();
    applyTriVelocity();
    updateTriRotation();
    draw();


document.onkeydown = function(event)

if(!event.keyCode)
    keycode = window.event.keyCode;
 else 
    keycode = event.keyCode;

console.log(event.keyCode);
switch(keycode)
    //left
    case 37:
    tri.rotation = -1;
    break;

    //up
    case 38:
    tri.throttle = true;
    break;

    //right
    case 39:
    tri.rotation = 1;
    break;

    //down
    case 40:
    tri.currentSpeed = 0;
    break;

    default:
    break;

;

document.onkeyup = function(event)

if(!event.keyCode)
    keycode = window.event.keyCode;
 else 
    keycode = event.keyCode;

console.log(event.keyCode);
switch(keycode)
    //left
    case 37:
    tri.rotation = 0;
    break;

    //up
    case 38:
    tri.throttle = false;
    break;

    //right
    case 39:
    tri.rotation = 0;
    break;

    //down
    case 40:

    break;

    default:
    break;

;

function updateTriRotation()
    if(tri.rotation == 1)
        tri.orientation += tri.rotationSpeed;
     else if (tri.rotation == -1)
        tri.orientation -= tri.rotationSpeed;
     else 
        tri.orientation += 0;
    


...

【问题讨论】:

你能给一个工作示例的链接吗?你能把它放在 jsfiddle 上吗? jsfiddle.net/6dMpF 它从左上角开始,因此您必须将其转过来并向下驶入画布。谢谢。 【参考方案1】:

问题描述

keyup 事件被触发,但有时最终的keydown 事件会直接在keyup 之后触发。诡异的?是的。

问题在于如何实现为保持键重复keydown 事件的方式:重复触发最后保持键的keydown 事件。由于优化的 JavaScript 代码执行的异步、单线程特性,有时可能会在相应的 keyup 事件被触发之后触发重复的 keydown 事件。

看看当我同时按住和释放键 [UP] 和 [RIGHT] in my JSFiddle example 时会发生什么。

Observed with Firefox 13.0.1 on Windows 7:

rotation: 1 key down: 39
rotation: 1 key down: 38
rotation: 1 key down: 38
rotation: 1 key down: 38
rotation: 1 key up: 38
rotation: 0 key up: 39
rotation: 1 key down: 38

您可以在此示例控制台输出中看到三件事:

    只有 [RIGHT] (38) 的 keydown 事件被重复触发。 当我松开按键时,[RIGHT] (38) 的keyup 事件触发。 最后一个预定的keydown 事件被触发,keyup 被执行之后。

这就是为什么即使没有按下任何键,您的 rotation 状态也会“卡住”值 1


解决方案

为避免这种情况,您可以在释放键时跟踪微秒。当 keydown 事件发生在 几微秒 之前,我们会默默地忽略它。

我在您的代码中引入了一个辅助对象 Key。 See it working in this JSFiddle example.

现在控制台输出如下:

rotation: 1 key down: 40 time: 1340805132040
rotation: 1 key down: 40 time: 1340805132071
rotation: 1 key down: 40 time: 1340805132109
rotation: 1 key up: 40 time: 1340805132138
rotation: 0 key up: 39 time: 1340805132153
key down: 40 release time: 1340804109335
keydown fired after keyup .. nothing to do.
rotation: 0 key down: 39 time: 1340805132191

我的解决方案受到this blog article 的启发,它更进一步解释了如何避免在使用 JavaScript 键事件进行游戏开发时遇到的草率和“单向”问题。绝对值得一读。

忽略延迟的keydown 事件的解决方法基本上如下所示:

var Key = 
    _pressed: ,
    _release_time: ,

    MAX_KEY_DELAY: 100,

    onKeydown: function(event) 

        // avoid firing of the keydown event when a keyup occured
        // just +/- 100ms before or after a the keydown event.
        // due to the asynchronous nature of JS it may happen that the
        // timestamp of keydown is before the timestamp of keyup, but 
        // the actual execution of the keydown event happens *after* 
        // the keyup.   Weird? Yes. Confusing!

        var time = new Date().getTime();
        if ( this._release_time[event.keyCode] &&
             time < this._release_time[event.keyCode] + this.MAX_KEY_DELAY ) 
            console.log('keydown fired after keyup .. nothing to do.');
            return false;
        

        this._pressed[event.keyCode] = true;

        // LOGIC FOR ACTUAL KEY CODE HANDLING GOES HERE
    ,

    onKeyup: function(event) 
        delete this._pressed[event.keyCode];
        this._release_time[event.keyCode] = new Date().getTime();

        // LOGIC FOR ACTUAL KEY CODE HANDLING GOES HERE
    
;

document.onkeydown = function(event)  return Key.onKeydown(event); ;
document.onkeyup = function(event)  return Key.onKeyup(event); ;

更新 #1 Find an updated (fixed) version of your code on JSFiddle.

【讨论】:

感谢您的详细回答...我尝试实现它,但没有奏效。但是我发现当我快速点击左、上、右时,它显示的键码是:keydown:37 33 35 keyup:33 35。我正在使用带有 Firefox 的 ubuntu 12.04。也许按此顺序按箭头键是homeend 键的快捷方式。 @Yoshima Find an updated (fixed) version of your code on JSFiddle. @Yoshima 关于 [HOME] & [END] 问题...您也可以将这些键码映射到正确的功能,或者修复您的浏览器/桌面环境设置;)不过,有还有我观察到的时间问题。 (可以解决,如我的回答所示) @Kaii 谢谢...你帮了大忙。【参考方案2】:

这个问题已经有将近 8 年的历史了,但我遇到了类似的问题并偶然发现了这个页面,所以如果它对其他人有帮助:

我的代码中出现了干扰问题,我在按键时使用了 Setadd 键盘事件,在按键时使用了 delete 它们。我觉得使用event.key 字段来区分按键而不是keyCode 来维护用户键盘上写的内容和我的代码解释的内容之间的语义映射是一种很好的做法。 (例如,我相信 QWERTY 键盘的 W 会报告与 AZERTY 键盘的 Z 相同的 keyCode,但没有法语键盘可供验证。)

这工作正常(我没有看到其他人报告的乱序事件的问题),直到修改器出现。我发现了两个问题:

...Shift 所在的位置。如果用户击键的顺序是(hold-shift、hold-R、release-shift、release-R),那么JS事件的顺序就是wrt。 event.key 会去(keydown-shift,keydown-R,keyup-shift,keyup-r)。换句话说,按下了R,但释放了r,所以我的SetR 卡住了。我对此的解决方案是将Set 变成Map,其中键是event.keyCode,值是在keydown 事件发生时报告的event.key。这样,当我得到一个 keyup 事件时,我只需 delete 对应于 keyCode 的映射键并且键不会卡住。我希望 option/alt 需要同样的处理。 ...Meta 键所在的位置(我的 Mac 上的命令;我猜是 Windows 上的控制?)。在这种情况下,一个序列(hold-meta、hold-K、release-meta、release-K)将生成事件(keydown-meta、keydown-k、keyup-meta)。所以 - K 的 keyup 事件确实丢失了,即使我确定要执行 event.preventDefault 以防止系统窃取我的事件。我希望我有一个更好的解决方案,但是当我看到 Meta 关键事件时,我现在正在做的是对整个地图进行核对。我应该郑重声明,我的代码实际上并没有使用 Meta 作为修饰符;我只是想确保它是安全的,而且当用户快速键入时,我的密钥处理程序似乎很容易卡住。

哦,还有,关于模糊 - 我正在对地图进行核弹。那么所有的赌注都被取消了......

【讨论】:

以上是关于为啥 onkeyup 事件不会在 Javascript 游戏中触发的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Stripe 事件不会触发并且不会被 webhook 接收?

为啥 UserControl 的 Unloaded 事件不会触发?

为啥鼠标右键单击在按下时会触发“pointerdown”事件,但在释放时不会触发“pointerup”事件?

为啥 jQuery.val(value) 不会从 DOM 元素中分派任何事件?

为啥连接的 onclick 事件不会触发?

为啥小弟我的canvas的鼠标事件不会触发