问题 在JavaScript中是否可以覆盖keydown重复延迟?


目标是手动设置保持键的“重复率”。

例如,当在文本框中按住X键时,我明白有 浏览器特定的重复按下字符的方法。在某些情况下,它会暂停,然后不断触发按下的键。在其他情况下,它根本不重复。我想通过强制按下的键以特定间隔重复来缓解这种情况,无论浏览器如何。

通过研究,我想出了一个基于计时器的尝试,但在Safari中,它不会重复这个角色。我有一个菜单系统,按住箭头滚动列表,但翻译动画和重复率不相互喜欢。

var repeating = false;
var repeatRateTimer = null;

$( document ).bind( 'keyup', function( input ) {
    if( repeatRateTimer != null )
    {
        clearTimeout( repeatRateTimer );
        repeatRateTimer = null;
    }

    repeating = false;
} );

$( document ).bind( 'keydown', function( input ) {
    input.preventDefault( );

    if( repeating == true )
    {
        if( repeatRateTimer != null )
        {
            clearTimeout( repeatRateTimer );
            repeatRateTimer = null;
        }
        else
        {
            repeatRateTimer = setTimeout( function( ){ repeating = false; }, 1000 );
        }

        return;
    }
    repeating = true;

    // ...keyboard logic
} );

我可能把整个事情搞砸了......我试图重新创建一个简化版本 这个SO帖子。但是,我觉得那里 具有 做一个更好的方法。有什么想法吗?

更新:

我们可以假设最终用户没有将其OS键盘重复率设置为大于我想要使用的速率(1000ms)。如果是,那么它应该回落到它们的重复率,因为它不会继续触发按键事件。如果不是(更可能是因为大多数人不修改它),那么我们将覆盖该行为以使其延迟我们指定的时间段。


5302
2017-07-06 03:55


起源

是不是操作系统设置的重复率? (例如,在Windows中,这可以通过控制面板/键盘属性进行设置。)因此用户不希望密钥以该速率重复吗? (这只是问题中的一个错字,你有两次keyup而不是keyup和keydown?) - nnnnnn
正确,它是特定于浏览器/操作系统的。我正在尝试创建一致性,特别是对于我的应用程序,我想手动设置它重复的速率。此外,感谢排在错字,@ nnnnnn。 - Quantastical
有没有理由你没有使用的代码 所以发帖 你引用了吗? - Jeffery To
@Jeffery主要原因是因为我想自己编写,所以我可以更好地理解它,并且能够随着应用程序的增长而修改,而不依赖于别人的想法。这是个人偏好,源于需要了解事情的原因和方式。我也没有得到同样的感觉“是的!我知道了!”当我只是把别人的代码丢进去的时候。我会引用其他人的想法和方向,但我不会公然复制别人的逻辑,特别是当它不是专门为我的应用程序设计的时候。 - Quantastical
@MrSlayer - 实际上我确实使用计时器。但不像 setInterval 功能我的 Delta Timer 火力更准确。因此,始终保持您期望的1000毫秒间隔。另外,我的代码为同时按下的不同键激发多个事件。所有这些都保持恒定的间隔。我最后一次检查它能够同时处理四个按键。但该数字是特定于操作系统的。我相信能够同时处理4个按键对于任何应用来说都足够了。您不希望用户发送垃圾密钥。 - Aadit M Shah


答案:


好吧,我弄清楚为什么我的例子没有循环。在keydown循环中,它在超时之前清除了超时:

if( repeatRateTimer != null )
{
    clearTimeout( repeatRateTimer );
    repeatRateTimer = null;
}
else
{
    repeatRateTimer = setTimeout( function( ){ repeating = false; }, 1000 );
}

超时应该在它到期后才被清除,所以 if 条件需要移入超时功能:

if( repeatRateTimer == null )
{
    repeatRateTimer = setTimeout( function( ) {
        repeating = false;
        clearTimeout( repeatRateTimer );
        repeatRateTimer = null;
    }, 1000 );
}

如果有人可以改进这一点,或者提供更好的选择,我将保留这笔赏金。


5
2017-07-08 14:37



这就是我所建议的,也就是处理了多少竞争条件 - 通过信号量(在这种情况下旋转锁定,你在旋转布尔实现的变量repeatRateTimer)。 - Travis J
@TravisJ,感谢您的介绍 信号。 - Quantastical


请看以下内容 JavaScript文件。如果向下滚动到第530行,您将找到以下类:

var Keyboard = new Class(function (constructor) {
    var key = {};

    var eventListener = {
        keyup: {},
        keydown: {},
        keypress: {}
    };

    constructor.overload(["Number"], function (interval) {
        setInterval(keypress, interval);
    });

    window.addEventListener("keyup", keyup, false);
    window.addEventListener("keydown", keydown, false);

    function keyup(event) {
        var keyCode = event.keyCode;
        var listener = eventListener.keyup[keyCode];
        key[keyCode] = false;
        if (listener)
        listener();
    }

    function keydown(event) {
        var keyCode = event.keyCode;
        var listener = eventListener.keydown[keyCode];
        key[keyCode] = true;
        if (listener)
        listener();
    }

    function keypress() {
        for (var code in key) {
            var listener = eventListener.keypress[code];
            if (key[code] && listener) listener();
        }
    }

    this.addEventListener = new Dispatcher(["String", "Number", "Function"], function (type, keyCode, listener) {
        type = eventListener[type];
        if (type) type[keyCode] = listener;
        else throw new Error("Unexpected value for type.");
    });
});

作者所做的是他创造了一个特别的 Keyboard 用于委派关键事件的类: keyupkeydown 和 keypress。该类只有一个构造函数接受一个参数 - 的间隔 keypress 事件(这是你想要的)。您可以使用以下命令添加事件侦听器 addEventListener 实例的方法 Keyboard 类:

var keyboard = new Keyboard(125); // fire key press 8 times a second.

keypress.addEventListener("keypress", 65, function () {
    // do something every time A is pressed
});

请注意,上面的类取决于以下框架: Lambda JS。您可以看到上述脚本的工作演示 这里。希望这可以帮助。

更新1:

你的  在Opera中不起作用。此外,第二个事件在Firefox中额外延迟500毫秒后触发,并且连续事件不会保持相同的间隔。此外,它无法同时处理多个关键事件。让我们纠正这个问题:

首先,我们需要为其创建一个简单的脚本 Delta Timing 这样,关键事件会在恒定间隔后触发。我们使用以下代码段来创建 DeltaTimer

function DeltaTimer(render, interval) {
    var timeout;
    var lastTime;

    this.start = start;
    this.stop = stop;

    function start() {
        timeout = setTimeout(loop, 0);
        lastTime = Date.now();
        return lastTime;
    }

    function stop() {
        clearTimeout(timeout);
        return lastTime;
    }

    function loop() {
        var thisTime = Date.now();
        var deltaTime = thisTime - lastTime;
        var delay = Math.max(interval - deltaTime, 0);
        timeout = setTimeout(loop, delay);
        lastTime = thisTime + delay;
        render(thisTime);
    }
}

接下来我们编写逻辑来解雇自定义 keypressed事件。我们需要自定义事件,因为我们必须能够同时处理多个键:

(function (interval) {
    var keyboard = {};

    window.addEventListener("keyup", keyup, false);
    window.addEventListener("keydown", keydown, false);

    function keyup(event) {
        keyboard[event.keyCode].pressed = false;
    }

    function keydown(event) {
        var keyCode = event.keyCode;
        var key = keyboard[keyCode];

        if (key) {
            if (!key.start)
                key.start = key.timer.start();
            key.pressed = true;
        } else {
            var timer = new DeltaTimer(function (time) {
                if (key.pressed) {
                    var event = document.createEvent("Event");
                    event.initEvent("keypressed", true, true);
                    event.time = time - key.start;
                    event.keyCode = keyCode;
                    window.dispatchEvent(event);
                } else {
                    key.start = 0;
                    timer.stop();
                }
            }, interval);

            key = keyboard[keyCode] = {
                pressed: true,
                timer: timer
            };

            key.start = timer.start();
        }
    }
})(1000);

interval 被设定为 1000 ms但你可以改变它。最后注册我们做的事件:

window.addEventListener("keypressed", function (event) {
    document.body.innerHTML += event.keyCode + " (" + event.time + " ms)<br/>";
}, false);

这是简单而有效的JavaScript。不需要jQuery。你可以看到 现场演示 在这里,看看你和我的脚本之间的区别。干杯。

更新2:

看着对方  在StackOverflow上,这是使用上述模式实现它的方法:

window.addEventListener("keypressed", function (event) {
    switch (event.keyCode) {
    case 37:
        Move(-1, 0);
        break;
    case 38:
        Move(0, -1);
        break;
    case 39:
        Move(1, 0);
        break;
    case 40:
        Move(0, 1);
        break;
    }
}, false);

使用上面的代码将消除您遇到的短暂延迟,并允许同时为不同的键触发多个事件。


4
2017-07-06 04:54



干杯的想法和信息。我之前从未使用过Lambda JS,我宁愿不必这样做;但是,它可以依靠或参考有关如何编写自己的想法。 - Quantastical
@MrSlayer - 我在阅读完答案后编辑了我的答案,并设法改进了你的代码。 =) - Aadit M Shah
很好,@ AaditMShah。在接下来的几天里,我一定会更深入地研究这个问题。我非常感谢您的努力和持续的兴趣。 - Quantastical
@MrSlayer - 不客气。 =) - Aadit M Shah


你怎么做自定义键事件。你可以听原始的(keyup / keydown),如果他们通过时间条件,你就会触发自定义事件。
这种方式的好处是您不依赖于计时器,它可以为您提供更多功能,因为您使用自定义事件(顺便说一下,如果您愿意,可以跳过取消事件部分)。
这是一个演示,看看我在说什么: http://jsfiddle.net/gion_13/gxEMz/
基本代码看起来像这样:

$(document).ready(function(){
    var dispatcher = $(window),
        keyRate = 1000, //ms
        lastKeyEvent = 0,
        cancelEvent = function(e){
            var evt = e ? e:window.event;
            if(evt.stopPropagation)    
                evt.stopPropagation();
            if(evt.cancelBubble!=null) 
                evt.cancelBubble = true;
            return false;
        };

    dispatcher
        .bind('keydown',function(e){
            var now = new Date().getTime();
            if(now - lastKeyEvent <= keyRate)
                // cancel the event
                return cancelEvent(e);
            var keyEventsTimeDiff = now - lastKeyEvent;
            lastKeyEvent = now;
            dispatcher.trigger('special-keydown',[e,keyEventsTimeDiff ]);
        })
        .bind('keyup',function(e){
            cancelEvent(e);
            dispatcher.trigger('special-keyup',[e]);
        })
        // binding the custom events
        .bind('special-keydown',function(e,keyEventsTimeDiff){
            console.log(e,'special keydown triggered again after ' + keyEventsTimeDiff +'ms');
        })
       .bind('special-keyup',function(e,keyEventsTimeDiff){
            console.log(e,'special keyup');
        });
});

2
2017-07-08 14:44