如何使用 rxjs 过滤按键?
How to filter keydowns with rxjs?
我需要清理 keydown/keyup 事件而不重复。当您按下一个键时,会发生 keydown 事件,当您释放时 - keyup。没有混乱的重复按键。
代码如下:
var keyDowns = rx.Observable.fromEvent(document, 'keydown');
var keyUps = rx.Observable.fromEvent(document, 'keyup');
var keyActions = rx.Observable.merge(keyDowns, keyUps);
keyActions.subscribe(function(e) {
console.log e
});
如何调整它来完成这项工作?
查看下面 Federico 的回答以获得更优雅和惯用的解决方案。
您可以使用 distinctUntilChanged
避免来自 observable 的重复项(正如您在按住键时看到的那样),它会过滤掉与最后发出的值匹配的值(其中匹配取决于发出值的相等性或它们的相等性) keySelector
回调参数转换的值)。
在您的情况下,这看起来可能像这样简单:
var keyDowns = Rx.Observable.fromEvent(document, 'keydown');
var keyUps = Rx.Observable.fromEvent(document, 'keyup');
var keyActions = Rx.Observable
.merge(keyDowns, keyUps)
.distinctUntilChanged(function(e) { return e.type + (e.key || e.which); });
keyActions.subscribe(function(e) {
console.log(e.type, e.key || e.which, e.keyIdentifier);
});
但是当持有多个键时,这是不够的。在 Chrome OS X 中,如果我按住 A、S、D,然后松开 A、S、D,我会在控制台中得到以下信息:
keydown 65 U+0041
keydown 83 U+0053
keydown 68 U+0044
keyup 65 U+0041
keydown 68 U+0044
keyup 83 U+0053
keydown 68 U+0044
keyup 68 U+0044
注意释放 A 和 S 时 "keydown 68" (D) 的重复。这是因为 keydown
事件仅针对最近按下的键重复。使用与您评论中的想法类似的想法,我们可以尝试使用自定义过滤器来维护已按下的键的列表:
var keyDowns = Rx.Observable.fromEvent(document, 'keydown');
var keyUps = Rx.Observable.fromEvent(document, 'keyup');
var keyActions = Rx.Observable
.merge(keyDowns, keyUps)
.filter((function() {
var keysPressed = {};
return function(e) {
var k = e.key || e.which;
if (e.type == 'keyup') {
delete keysPressed[k];
return true;
} else if (e.type == 'keydown') {
if (keysPressed[k]) {
return false;
} else {
keysPressed[k] = true;
return true;
}
}
};
})());
keyActions.subscribe(function(e) {
console.log(e.type, e.key || e.which, e.keyIdentifier);
});
按住 A、S、D,然后松开 A、S、D 现在得到:
keydown 65 U+0041
keydown 83 U+0053
keydown 68 U+0044
keyup 65 U+0041
keyup 83 U+0053
keyup 68 U+0044
这类似于您在评论中提到的方法,只是我们过滤组合流而不是预过滤,以便组合流看起来像我们喜欢的那样。
我对 rx.js 的经验不足,无法断言这是执行此操作的最佳方法,但它确实感觉更符合 Rx 风格。 (任何评论表示赞赏。)
虽然它很好用,但 icio 的状态跟踪解决方案是 ** 非常 ** 单一。正确的 RxJs 5 解决方案是:
var keyDowns = Rx.Observable.fromEvent(document, "keydown")
var keyUps = Rx.Observable.fromEvent(document, "keyup")
var keyPresses = keyDowns
.merge(keyUps)
.groupBy(e => e.keyCode)
.map(group => group.distinctUntilChanged(null, e => e.type))
.mergeAll()
解释:
- 合并所有键的所有 keydown 和 keyup 事件。
- 按键 (keyCode) 对它们进行分组。它创建一个可观察对象的可观察对象,其中每个子可观察对象将包含一个键的所有事件。
- 将每个子可观察对象映射到其自身的 distinctUntilChanged 版本。按事件类型执行,以便跳过额外的按键。
- 将所有键合并回一个 observable
在 RxJs 5 中,如果您只想过滤单个键,则为:
import { fromEvent } from 'rxjs/observable/fromEvent';
import { merge } from 'rxjs/operators/merge';
import { distinctUntilChanged, filter } from 'rxjs/operators';
const keyDowns = fromEvent(document, 'keydown');
const keyUps = fromEvent(document, 'keyup');
const keyPresses = keyDowns.pipe(
merge(keyUps),
filter((e: KeyboardEvent) => e.keyCode === 17),
distinctUntilChanged((x, y) => x.type == y.type)
);
keyPresses.subscribe((ctrlpress) => {
this.holdingCtrl = ctrlpress.type === 'keydown';
});
我需要清理 keydown/keyup 事件而不重复。当您按下一个键时,会发生 keydown 事件,当您释放时 - keyup。没有混乱的重复按键。
代码如下:
var keyDowns = rx.Observable.fromEvent(document, 'keydown');
var keyUps = rx.Observable.fromEvent(document, 'keyup');
var keyActions = rx.Observable.merge(keyDowns, keyUps);
keyActions.subscribe(function(e) {
console.log e
});
如何调整它来完成这项工作?
查看下面 Federico 的回答以获得更优雅和惯用的解决方案。
您可以使用 distinctUntilChanged
避免来自 observable 的重复项(正如您在按住键时看到的那样),它会过滤掉与最后发出的值匹配的值(其中匹配取决于发出值的相等性或它们的相等性) keySelector
回调参数转换的值)。
在您的情况下,这看起来可能像这样简单:
var keyDowns = Rx.Observable.fromEvent(document, 'keydown');
var keyUps = Rx.Observable.fromEvent(document, 'keyup');
var keyActions = Rx.Observable
.merge(keyDowns, keyUps)
.distinctUntilChanged(function(e) { return e.type + (e.key || e.which); });
keyActions.subscribe(function(e) {
console.log(e.type, e.key || e.which, e.keyIdentifier);
});
但是当持有多个键时,这是不够的。在 Chrome OS X 中,如果我按住 A、S、D,然后松开 A、S、D,我会在控制台中得到以下信息:
keydown 65 U+0041
keydown 83 U+0053
keydown 68 U+0044
keyup 65 U+0041
keydown 68 U+0044
keyup 83 U+0053
keydown 68 U+0044
keyup 68 U+0044
注意释放 A 和 S 时 "keydown 68" (D) 的重复。这是因为 keydown
事件仅针对最近按下的键重复。使用与您评论中的想法类似的想法,我们可以尝试使用自定义过滤器来维护已按下的键的列表:
var keyDowns = Rx.Observable.fromEvent(document, 'keydown');
var keyUps = Rx.Observable.fromEvent(document, 'keyup');
var keyActions = Rx.Observable
.merge(keyDowns, keyUps)
.filter((function() {
var keysPressed = {};
return function(e) {
var k = e.key || e.which;
if (e.type == 'keyup') {
delete keysPressed[k];
return true;
} else if (e.type == 'keydown') {
if (keysPressed[k]) {
return false;
} else {
keysPressed[k] = true;
return true;
}
}
};
})());
keyActions.subscribe(function(e) {
console.log(e.type, e.key || e.which, e.keyIdentifier);
});
按住 A、S、D,然后松开 A、S、D 现在得到:
keydown 65 U+0041
keydown 83 U+0053
keydown 68 U+0044
keyup 65 U+0041
keyup 83 U+0053
keyup 68 U+0044
这类似于您在评论中提到的方法,只是我们过滤组合流而不是预过滤,以便组合流看起来像我们喜欢的那样。
我对 rx.js 的经验不足,无法断言这是执行此操作的最佳方法,但它确实感觉更符合 Rx 风格。 (任何评论表示赞赏。)
虽然它很好用,但 icio 的状态跟踪解决方案是 ** 非常 ** 单一。正确的 RxJs 5 解决方案是:
var keyDowns = Rx.Observable.fromEvent(document, "keydown")
var keyUps = Rx.Observable.fromEvent(document, "keyup")
var keyPresses = keyDowns
.merge(keyUps)
.groupBy(e => e.keyCode)
.map(group => group.distinctUntilChanged(null, e => e.type))
.mergeAll()
解释:
- 合并所有键的所有 keydown 和 keyup 事件。
- 按键 (keyCode) 对它们进行分组。它创建一个可观察对象的可观察对象,其中每个子可观察对象将包含一个键的所有事件。
- 将每个子可观察对象映射到其自身的 distinctUntilChanged 版本。按事件类型执行,以便跳过额外的按键。
- 将所有键合并回一个 observable
在 RxJs 5 中,如果您只想过滤单个键,则为:
import { fromEvent } from 'rxjs/observable/fromEvent';
import { merge } from 'rxjs/operators/merge';
import { distinctUntilChanged, filter } from 'rxjs/operators';
const keyDowns = fromEvent(document, 'keydown');
const keyUps = fromEvent(document, 'keyup');
const keyPresses = keyDowns.pipe(
merge(keyUps),
filter((e: KeyboardEvent) => e.keyCode === 17),
distinctUntilChanged((x, y) => x.type == y.type)
);
keyPresses.subscribe((ctrlpress) => {
this.holdingCtrl = ctrlpress.type === 'keydown';
});