12

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.

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.

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 has to be a better way of doing this. Any thoughts?

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.

4

4 に答える 4

5

次の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。このクラスには、単一の引数を受け入れるコンストラクターが1つだけあります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
});

上記のクラスは次のフレームワークに依存していることに注意してください:LambdaJS。上記のスクリプトの動作デモをここで見ることができます。お役に立てれば。

アップデート1:

コードがOperaで機能しません。さらに、Firefoxでさらに500ミリ秒の遅延が発生すると、2番目のイベントが発生し、連続するイベントは同じ間隔を維持しません。さらに、複数の重要なイベントを同時に処理することはできません。この問題を修正しましょう:

まず、キーイベントが一定の間隔で発生するように、DeltaTimingの簡単なスクリプトを作成する必要があります。次のスニペットを使用して:を作成し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);

はmsintervalに設定されて1000いますが、変更できます。最後に、イベントを登録します。

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

上記のコードを使用すると、発生している短い遅延が解消され、異なるキーに対して同時に複数のイベントが発生する可能性があります。

于 2012-07-06T04:54:44.770 に答える
5

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

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 if condition needed to be moved into the timeout function:

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.

于 2012-07-08T14:37:36.197 に答える
2

カスタムキーイベントを作成するのはどうですか。元のイベント(キーアップ/キーダウン)を聞くことができ、それらが時間条件を通過した場合、カスタムイベントをトリガーします。
この方法には、タイマーに依存しないという利点があり、カスタムイベントを使用するため、より強力になります(ただし、必要に応じて、キャンセルイベントの部分をスキップできます)。
これが私が話していることを確認するためのデモです: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');
        });
});
于 2012-07-08T14:44:31.217 に答える
0

It is an old post but I want to share my current answer with rxjs. I had to do a Tetris game in Javascript and I wanted to change the keydown repeat delay to have a better gameplay.

Indeed, we can't override the real keydown repeat delay property which is specific to the OS. However we can simulate it thanks to the keyup and keydown events.

Finally, I came up with this (typescript) :

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 version:

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) => {
    ...
  }
});

The keyPressed$ function take a keyboard key as argument and return an observable. This observable emits immediately when the corresponding key is pressed then wait for 150ms and will emit each 50ms.

You can change these values for your case in the timer function.

Don't forget to unsubscribe from the observable.

于 2021-04-12T08:21:51.100 に答える