监听 JS 中的击键模式

Listen for key-stroke patterns in JS

在 JavaScript 中,我知道我可以监听特定的键码,但我如何才能监听特定的击键“模式”,然后在键入这些模式时触发不同的事件?

这方面的一个例子可能是玩家按下 Konami 密码的键(上、上、下、下、左、右、左、右、B、A、开始)的游戏。我如何在 JavaScript 中跟踪该模式或类似的其他模式?

我是否可以通过某种 await/async 过程来捕获每个键并等待下一个键来完成此操作?有没有一种更简单的方法可以在不耗尽最终用户系统的情况下解决这个问题?

我想做这样的伪代码:

on('keydown', () => {
    if (lastKeysPressed === "QWERTY") doSomething();
});

我目前尝试的是将键入的每个键记录到一个字符串中,然后读取键码序列以查看它是否匹配,如下所示:

let typedString = "";
const secret = "This is a secret";
document.addEventListener('keydown', e => {
    typedString += e.key;
    if (typedString.slice(-secret.length) === secret) alert('Secret typed!');
});

这是迄今为止我得到的最接近的结果,到目前为止它对我来说适用于一个字符串,但我很难让它适用于要侦听的多个字符串以及当字符串中有大写字母时字符串的中间,因为 keydown 事件也捕捉到“Shift”键并将 Shift 附加到我的 typedString.

就个人而言,我会通过将每个击键的 keyCode 存储到一个数组中,然后检查该键盘记录器数组中的 last X 个项目并匹配来解决这个问题与您想要收听的模式相对应。

为了保持你提到的数组轻量级,我会 trim 每次击键的数组长度到你最长模式的数组长度,这将是跟踪你所有模式所需的最大击键次数.

当比较最近输入的键时,我将新的键代码取消移位以将它们添加到 keylog 数组的开头,然后将其与每个模式的反转状态进行比较以从右到左边。这是至关重要的,因为我们不断 trim 从数组中删除最后一个字符,即最旧的字符。最近的字符将始终出现在 keylog 数组中的最前面。为了对模式进行浅反转,以免改变原始模式的顺序,我使用 .slice().reverse().

最后,为了获得准确的小写和大写字母的keyCodes,如果事件的key 属性正好是一个字符,我取e.key.charCodeAt()。如果没有这个,所有字母都将呈现为大写,并且某些字符(例如“斜线”)将具有错误的 charCode。

在这里试试:

patterns = {
    "konami code": [38,38,40,40,37,39,37,39,98,97,13],
    "happy birthday": "happy birthday".split("").map(e => e.charCodeAt()),
    "sticky keys": [16,16,16,16,16],
    "hello world": "hello world".split("").map(e => e.charCodeAt()),
    "todays date": `${((new Date()).getMonth() + 1).toString()}/${(new Date()).getDate().toString()}/${(new Date()).getFullYear().toString()}`.split("").map(e => e.charCodeAt())
};

const maxLength = Math.max(...Object.values(patterns).map(e => e.length));
const keylog = [];
const testArraysEqual = (arr1, arr2) => arr1.length === arr2.length && arr1.every((e,i) => e === arr2[i]);

document.addEventListener('keydown', e => {
    keylog.unshift(e.key.length === 1 ? e.key.charCodeAt() : e.keyCode);
    if (keylog.length > maxLength) keylog.length = maxLength;
    for ([patternName, pattern] of Object.entries(patterns)) {
        if (pattern.length <= keylog.length && testArraysEqual(pattern.slice().reverse(), keylog.slice(0, pattern.length))) {
            console.log(`${patternName} pattern fired`);
        }
    }
});
Try clicking anywhere in this result window and typing any of the following patterns:
<ul>
    <li>↑ ↑ ↓ ↓ ← → ← → b a "enter"</li>
    <li>happy birthday</li>
    <li>"shift" "shift" "shift" "shift" "shift"</li>
    <li>hello world</li>
    <li>today's date in M/D/YYYY format (e.g. `2/22/2021`)</li>
</ul>