- 任意数量键组合监听
- 输入顺序检测,完全对应检测
- n连击监听
- 按下/放开响应
- 上下文切换绑定监听
- 全局响应/暂停监听
- 绑定到'key' | 'keyCode' | 'code'三种不同类型的键盘事件
- Typescript支持
import Keyboard, { AvalidKeys } from 'keyboard.util';
Keyboard是个单例, AvalidKeys是keyboard.util定义的合法绑定键名的ts类型。
Keyboard.mount();
Keyboard.unmount();
Keyboard做了事件委托,只绑定一个监听事件到document。 首先在任意位置调用一次Keyboard.mount()使得Keyboard响应已绑的监听。 在需要暂停响应监听的时候调用Keyboard.unmount(),需要响应监听的时候再次调用Keyboard.mount();
Keyboard.bind('a', () => {
console.log('pressDown a')
}, () => {
console.log('pressUp a')
});
-
1.bindKeys: 需要监听的键 (必须,键名必须为MapEventKeyCode.ts文件里定义的范畴) bindKeys可为三种格式:
- string类型 (格式为:键名+键名+...,中间可有可无空格)
Keyboard.bind('a + b'); // 监听a+b Keyboard.bind('a+b + c'); // 监听a+b+c Keyboard.bind('q + q + q'); // 监听q三连击 Keyboard.bind('a + b | c + d'); // 同时监听a+b和c+d
- Array/类型
Keyboard.bind(['a', 'b']); // 监听a+b Keyboard.bind(['a', 'b', 'c']; // 监听a+b+c Keyboard.bind(['q', 'q', 'q'); // 监听q三连击
- Array类型
Keyboard.bind([['a', 'b'], ['c', 'd']]); // 同时监听a+b和c+d
Array类型的优势在于,typescript下有类型提示和校验。但这也带来语义不足的问题:
绑定['a', 'b', 'a'], 'a + a + b'这种带有重复键名的多键组合的,出于语义上的考量会把重复的键给去重,而不是表示为依次顺序按下。
打个比方: string类型的输入可以多一种 'a > b > c'的写法表示顺序按下,但是Array貌似不能再多一种语义了。
所以为了string和Array语义等价,也没有在string上额外加一种功能表示顺序按下。
我觉得用纯重复键名组合表示连击这种方式都已经是一种很Hack的做法……但是为了Ts的支持,先就这样吧。
Ts4.1出了个template string type,本质是转化到union type,键名数量级太大也不能用在这里,但是说不定以后进化了。
bindKeys如不符合上述三种类型和或键名不合法,会导致bind函数失败返回false
- string类型 (格式为:键名+键名+...,中间可有可无空格)
-
2.pressDownExectuor: 按下按键时的响应函数 (必须, Function或者显式的null)
-
3.pressUpExectuor: 松开按键时的响应函数 (可选, Function)
- bindKeys表示连击时绑pressUpExectuor会被忽视
bind成功会返回一个包含了所有生成的监听器对象的数组
bind函数的第一个参数除了可以是上述bindKeys类型,还可以是符合如下规则的对象(此时对象的bindKeys键为必填属性): 使用bindKeys时生成的监听器拥有下述属性的默认值 等同 使用bindOption而不填属性值时的默认值
interface BindOption {
bindKeys: BindKeys;
type?: 'key' | 'keyCode' | 'code'; // default 'key'
context?: string;
preventRepeat?: boolean; // default true
strictOrder?: boolean | 'equal'; // default true
caseSensitive?: boolean; // default false
}
每个键盘事件KeyboardEvent中有三种类型 'key' | 'keyCode' | 'code' 标志按下的是哪个键。
事件对象的 key 属性允许获取字符,而事件对象的 code 属性则允许获取“物理按键代码”。
例如,同一个按键 Z,可以与或不与 Shift 一起按下。会得到两个不同的字符:小写的 z 和大写的 Z。
event.key 正是这个字符,并且它将是不同的。但是,event.code 是相同的:
如果用户使用不同的语言,那么切换到另一种语言将产生完全不同的字符,而不是 "Z"。它将成为 event.key 的值,而 event.code 则始终都是一样的:"KeyZ"。
因为特殊物理配列的键盘实在太多了(在我眼里),所以还是建议使用'key'。即使这样子会导致开着非英文输入法时候无效。
'code'是'keyCode'的新标准, 我还没写填上。
只有当bindOption type 为'key'时才有意义,所以默认设为false。
比如当按下shitf + xx的组合键时,event.key是大小写敏感的,但是在我想当然的大部分场景下仅仅只是想把shift当组合键而不是触发大写语义。
Keyboard.bind({
bindKeys: ['v', 'b', 'n'],
preventRepeat: false,
}, () => {
console.log('repeat press v and b and n');
});
一直按着键盘不放会持续触发键盘事件,显式设置这个参数为false使监听器响应重复事件。
Keyboard.bind({
bindKeys: ['j', 'k', 'l'],
strictOrder: false,
}, () => {
console.log('press j and k and l');
});
strictOrder默认为true,即只有按照key在数组中的顺序才会触发响应 如下必须依次jkl
strictOrder可以为'equal'进行更严格校验, 即 只 顺序按着key, 如果在之前还额外按着别的键如下h->j->k->l则不触发
显式设置strictOrder为false去掉顺序校验
Keyboard.getContext();
Keyboard.setContext('context');
Keyboard.getAllContext();
Keyboard.bind({
bindKeys: ['a'],
context: 'context2'
}, () => {
console.log('press a in context2')
});
Keyboard有上下文的概念,可以在bindOption中设置context值显式为一个监听器指定生效的上下文。
默认为undefined即在任何上下文中都生效
- 通过getContext获取Keyboard当前Context
- 通过setContext设置Keyboard当前Context
- 通过getAllContext获取Keyboard中所有有context的监听器的context集合
const listener1 = Keyboard.bind('t', () => {});
const listener2 = Keyboard.bind(['g', 'h'], () => {});
if (listener1 && listener2) {
Keyboard.unbind(listener1[0]);
Keyboard.unbind([...listener2, ...listener1]);
}
bind成功会返回包含生成的监听器对象的数组,可以拿这个对象数组去unbind
也可以拿单个监听器对象
有任一解绑成功则返回true, 否则返回false
console.log(Keyboard.getAllListener());
getAllListener可以拿到Keyboard中所有的bind生成的监听器对象
通过setContinuousInterval设置连击检测的时间间隔,默认为 250。
代表两次相同按键超过250ms不算连击
Keyboard.setContinuousInterval(222);
reset可以清空Keyboard中已经绑定的监听器和上下文
Keyboard.reset();
MacOS下基于command的组合键有个系统层级的问题
当按住command时候再按其他任意键触发组合键,这时候接着按住command,松开其他键,松开的键的keyup事件不会被浏览器触发。
所以在command松开的时候全清_pressed里监测的已按下的按键防止bug,但是按住command再连续按其他键这种操作方式没有解决。
如果只是跟踪command + 任意一键,可以通过keyEvent里判定是否同时按下command解决,但是三键以上组合这个问题几乎无解。