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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11355595/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 12:53:35  来源:igfitidea点击:

Is it possible to override the keydown repeat delay, in JavaScript?

javascriptjquerykeypresskeydownrepeat

提问by webdevkit

The objective is manually set a held key's "repeat rate".

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

For example, when in a text box and pressing and holding the X key, I understand that there is browser-specific ways of repeating the pressed character. In some, it pauses, then continuously triggers the pressed key. In others, it doesn't repeat at all. I want to mitigate this by forcing the pressed key to repeat at a specific interval, regardless of browser.

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

Through research, I've come up with a timer-based attempt, but in Safari, it does not repeat the character. I've got a menu system where holding down arrow scrolls through the list, but the translation animation and repeat rate don't like each other.

通过研究,我想出了一个基于计时器的尝试,但在 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
} );

I may have botched this whole thing up...I tried to recreate a simplified version of this SO post. However, I feel there hasto be a better way of doing this. Any thoughts?

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

Update:

更新:

We can assume that the end-user hasn't set their OS keyboard repeat rate greater than the rate I want to use (1000ms). If it is, then it should fall back to their repeat rate, since it won't keep triggering the key press event. If it isn't (more likely since most people don't modify that), then we would be overriding that behavior to make it delay our specified period.

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

采纳答案by webdevkit

Well, I figured out why my example wasn't looping. In the keydown loop, it was clearing the timeout before it expired:

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

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

The timeout should be cleared only after it expires, so the ifcondition needed to be moved into the timeout function:

超时应在超时后清除,因此if需要将条件移动到超时函数中:

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

I'll leave this bounty open in case someone can improve upon this, or provide a better alternative.

如果有人可以对此进行改进或提供更好的选择,我将保留此赏金。

回答by Aadit M Shah

Look at the following JavaScript file. If you scroll down to line 530 you will find the following class:

查看以下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.");
    });
});

What the author has done is that he has created a special Keyboardclass for delegating the key events: keyup, keydownand keypress. The class has only one constructor which accepts a single argument - the interval of the keypressevent (which is what you want). You can add event listeners using the addEventListenermethod of the instance of the Keyboardclass:

作者所做的是他创建了一个特殊的Keyboard类来委托关键事件:keyup,keydownkeypress。这个类只有一个构造函数,它接受一个参数——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
});

Note that the above class depends on the following framework: Lambda JS. You can see a working demo of the above script here. Hope this helps.

请注意,上述类依赖于以下框架:Lambda JS。您可以在此处查看上述脚本的工作演示。希望这可以帮助。

Update 1:

更新 1:

Your codedoes not work in Opera. In addition the second event fires after a extra 500 ms delay in Firefox and consecutive events do not maintain the same interval. Plus it can't handle multiple key events at the same time. Let's rectify this problem:

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

First we need to create a simple script for Delta Timingso that the key events fire after constant interval. We use the following snippet for creating a DeltaTimer:

首先,我们需要为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);
    }
}

Next we write the logic to fire custom keypressedevents. We need custom events since we must be able to handle multiple keys at the same time:

接下来我们编写触发自定义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);

The intervalis set at 1000ms but you may change that. Finally to register an event we do:

interval设置在1000MS,但你可以改变这种状况。最后注册一个事件,我们这样做:

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

This is simple and efficient JavaScript. No jQuery required. You can see the live demohere, and see the difference between your script and mine. Cheers.

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

Update 2:

更新 2:

Looking at the other questionon StackOverflow, this is how you would implement it using the above pattern:

查看有关 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);

Using the above code will remove the short delay you're experiencing and also allow multiple events to be fired for different keys at the same time.

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

回答by gion_13

How about you make custom key events. you can listen to the original ones (keyup/keydown) and if they pass the time condition, you trigger your custom event.
This way has the benefit that you do not rely on timers and it gives you more power, because you use custom events (btw, you can skip the cancel event part if you wish).
Here's a demo to see what I'm talking about : http://jsfiddle.net/gion_13/gxEMz/
And the basic code looks something like this :

您如何制作自定义键事件。您可以收听原始事件(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');
        });
});