【问题标题】:Is it possible to override the keydown repeat delay, in JavaScript?是否可以在 JavaScript 中覆盖 keydown 重复延迟?
【发布时间】:2012-07-06 12:32:22
【问题描述】:

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

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

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

我可能把整个事情搞砸了……我试图重新创建一个简化版的this SO post。但是,我觉得必须有更好的方法来做到这一点。有什么想法吗?

更新:

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

【问题讨论】:

  • 重复率不是操作系统设置的吗? (例如,在 Windows 中,这可以通过控制面板/键盘属性进行设置。)所以用户不希望按键以这种速率重复吗? (你有两次 keyup 而不是 keyup 和 keydown 的问题只是一个错字吗?)
  • Correct,它是特定于浏览器/操作系统的。我正在尝试创建一致性,特别是对于我的应用程序,我想手动设置它重复的速率。另外,感谢@nnnnnn 对错字的提醒。
  • 您是否有理由不使用您引用的SO post 中的代码?
  • @JefferyTo 主要原因是因为我想自己编写它,这样我可以更好地理解它并且能够随着我的应用程序的增长进行修改,而不依赖于别人的想法。这是一种个人偏好,源于需要了解事物为什么以及如何工作的需要。我也没有那种“是的!我明白了!”的感觉。当我只是把别人的代码放进去时。我会参考别人的想法和方向,但我不会公然抄袭别人的逻辑,尤其是当它不是专门为我的应用程序设计的时候。
  • @MrSlayer - 实际上我确实使用计时器。然而,与setInterval 函数不同,我的Delta Timer 触发更准确。因此,您期望的 1000 毫秒间隔始终保持不变。此外,我的代码会为同时按下的不同键触发多个事件。它们都保持恒定的间隔。上次我检查它能够处理同时按下的四个键。但是,该数字是特定于操作系统的。我相信能够同时处理 4 个按键对于任何应用程序来说都足够了。您不希望用户向密钥发送垃圾邮件。

标签: javascript jquery keypress keydown repeat


【解决方案1】:

这是一篇旧帖子,但我想与 rxjs 分享我当前的答案。我必须用 Javascript 做一个俄罗斯方块游戏,我想改变按键重复延迟以获得更好的游戏玩法。

确实,我们无法覆盖特定于操作系统的真正的 keydown 重复延迟属性。 不过我们可以通过 keyup 和 keydown 事件来模拟它。

最后,我想出了这个(打字稿):

function keyDown$(key: KeyboardKey): Observable<KeyboardEvent> {
  return fromEvent<KeyboardEvent>(document, 'keydown').pipe(filter(event => event.key === key));
}

function keyUp$(key: KeyboardKey): Observable<KeyboardEvent> {
  return fromEvent<KeyboardEvent>(document, 'keyup').pipe(filter(event => event.key === key));
}

export function keyPressed$(key: KeyboardKey): Observable<KeyboardEvent> {
  return merge(keyDown$(key), keyUp$(key)).pipe(
    filter(event => event.repeat === false),
    switchMap(event => (event.type === 'keydown' ? merge(of(event), timer(150, 50).pipe(mapTo(event))) : NEVER))
  );
}

...

keyPressed$('ArrowRight').subscribe({
  next: (event) => {
    ...
  }
});

JS 版本:

function keyDown$(key) {
  return fromEvent(document, 'keydown').pipe(filter(event => event.key === key));
}

function keyUp$(key) {
  return fromEvent(document, 'keyup').pipe(filter(event => event.key === key));
}

export function keyPressed$(key) {
  return merge(keyDown$(key), keyUp$(key)).pipe(
    filter(event => event.repeat === false),
    switchMap(event => (event.type === 'keydown' ? merge(of(event), timer(150, 50).pipe(mapTo(event))) : NEVER))
  );
}

...

keyPressed$('ArrowRight').subscribe({
  next: (event) => {
    ...
  }
});

keyPressed$ 函数将键盘键作为参数并返回一个 observable。当按下相应的键时,这个 observable 会立即发射,然后等待 150 毫秒,然后每 50 毫秒发射一次。

您可以在计时器函数中根据您的情况更改这些值。

别忘了取消订阅 observable。

【讨论】:

    【解决方案2】:

    好吧,我明白了为什么我的示例没有循环播放。在 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 );
    }
    

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

    【讨论】:

    【解决方案3】:

    看看下面的JavaScript file。如果你向下滚动到第 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 类来委派关键事件:keyupkeydownkeypress。该类只有一个接受单个参数的构造函数 - keypress 事件的间隔(这是您想要的)。您可以使用Keyboard 类实例的addEventListener 方法添加事件监听器:

    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。您可以看到上述脚本here 的工作演示。希望这会有所帮助。

    更新 1:

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

    首先,我们需要为Delta Timing 创建一个简单的脚本,以便按键事件在恒定间隔后触发。我们使用以下 sn-p 来创建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 毫秒,但您可以更改它。最后注册一个我们做的事件:

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

    这是简单而高效的 JavaScript。不需要 jQuery。您可以在此处查看live demo,并查看您的脚本和我的脚本之间的区别。干杯。

    更新 2:

    查看 StackOverflow 上的另一个 question,这就是您使用上述模式实现它的方式:

    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);
    

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

    【讨论】:

    • 为这个想法和信息干杯。我以前从未使用过 Lambda JS,我宁愿不必;但是,这是可以依靠或参考的东西,以了解如何编写自己的想法。
    • @MrSlayer - 我在阅读您的答案后编辑了我的答案,并设法改进了您的代码。 =)
    • 很好,@AaditMShah。在接下来的几天里,我肯定会对此进行更深入的研究。我非常感谢您的努力和持续的兴趣。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-17
      • 1970-01-01
      • 2011-01-03
      • 2020-07-10
      • 2012-03-16
      • 2017-03-11
      • 2014-01-07
      • 2019-06-20
      相关资源
      最近更新 更多