完成键盘的开发

master
宇天 2020-11-03 17:58:13 +08:00
parent d6ca6d7133
commit 6c907b4e6d
3 changed files with 290 additions and 63 deletions

View File

@ -1,16 +1,40 @@
## JS键盘热键 ## JS键盘热键
> 支持各种按钮组合。 > 支持各种按钮组合。原生js开发, 无任何依赖(不到`2KB`)。使用也非常简单。
>> 1.0版 功能键(Shift, Ctrl, Alt/Option, Win/Cmd)不区分左右。 >> 1.0版 功能键(Shift, Ctrl, Alt/Option, Win/Cmd)不区分左右。
![keyboard](./keyboard.jpg) ![keyboard](./keyboard.jpg)
### 使用
> 键盘
```js
import Keyboard from 'http://unpkg.bytedo.org/@bytedo/keyboard/dist/index.js'
var kb = new Keyboard()
// 同时按Ctrl键和C键, 触发回调
kb.on(['ctrl + c'], ev => {
// todo...
})
// 分别按下 Ctrl + C键, 然后再 300毫秒内按下Ctrl + V键, 触发回调
kb.on(['ctrl + c', 'ctrl + v'], ev => {
// todo...
})
```
### 辅助功能键 ### 辅助功能键
> 辅助功能键, 不支持单独设置热键。 > 辅助功能键, 不支持单独设置热键。
>> 包括 `Ctrl、Shift、Alt/Option、Win/Cmd` 这4个。 >> 包括 `Ctrl、Shift、Alt/Option、Win/Cmd` 这4个。
## 普通按键
### 普通按键
> 即除了辅助功能键以外的其他按键。可以单独设置, 也可以配合辅助按键组合使用。 > 即除了辅助功能键以外的其他按键。可以单独设置, 也可以配合辅助按键组合使用。
>> 但是, 不允许在一组里出现多次。如需要, 请分组。 >> 但是, 不允许在一组里出现多次。如需要, 请分组。
@ -31,3 +55,68 @@ kb.on(['ctrl + c', 'ctrl + v'], ev => {
``` ```
### API
+ .on(actions<Array>, callback<Function>)
> 监听键盘动作组合, 支持单组或双组。
>> 双组时, 2组按键前后时差不能超过`300毫秒`, 否则视为2次独立的操作。
>> **键名不区分大小写**
---
+ .off(actions<Array>, callback<Function>)
> 移除键盘监听。
---
+ .destroy()
> 销毁整个键盘监听。
### 键名对照表
> 键名不区别大小写, 内部统一转为小写。
| 原始按键 | 修正后的键名 | 说明 |
| :-: | :-: | - |
| 0-9 | 0-9 | 数字键直接用阿拉伯数字, 不区别主键位和小数字键盘位 |
| A-Z | a-z | 字母键同样不变, 直接原样使用 |
| F1-F12 | f1-f12 | fn功能键,同样对应 |
| Prt | f13/print | 2种写法都可以 |
| Scr | f14/screen | 2种写法都可以 |
| Pau | f15/pause | 2种写法都可以 |
| left | left | 方向键 |
| right | right | 方向键 |
| up | up | 方向键 |
| down | down | 方向键 |
| Pg▴ | pageup | 向上翻页 |
| Pg▾ | pagedown | 向下翻页 |
| Home | home | Home键 |
| End | end | End键 |
| Ins | insert | 插入键 |
| Del | delete | 删除键(注意不是回退键) |
| Esc | esc | 退出键(左上角) |
| Menu | menu | 菜单键(部分键盘没有这个按键) |
| Caps | capslock | 大写锁定键(这个键的使用要特别注意) |
| Numlock | numlock | 数字键锁定键(87键以下的键盘没有) |
| Backspace | backspace | 回退键(=号键右边那个) |
| Tab | tab | 制表符键 |
| Cmd/Win | meta | Command/Win键(1.0版不区别左右) |
| Space | space | 空格键 |
| Ctrl | ctrl | Ctrl键(1.0版不区分左右) |
| Shift | shift | Shift键(1.0版不区分左右) |
| Alt/Option| alt | Alt/Option键(1.0版不区分左右) |
| Enter | enter | 回车键(1.0版不区分小数字键盘的回车) |
| ' | ' | 单引号键 |
| * | * | 小数字键盘中的乘号 |
| + | + | 小数字键盘中的加号 |
| - | - | 小数字键盘中的减号;及主键盘中的减号 (1.0版不区分) |
| / | / | 小数字键盘中的除号;及主键盘中的斜杠 (1.0版不区分) |
| 。 | . | 小数字键盘中的小数点;及主键盘中的斜句号 (1.0版不区分) |
| , | , | 逗号 |
| ; | ; | 分号 |
| = | = | 等号 |
| [ | [ | 左边中括号 |
| ] | ] | 右边中括号 |
| ` | ` | 反引号(Tab上面的键) |
| \ | \ | 反斜杠 |

View File

@ -6,8 +6,6 @@
import { KEY_DICT, MULTI_KEYS, MULTI_KEY_CODES } from './key.dict.js' import { KEY_DICT, MULTI_KEYS, MULTI_KEY_CODES } from './key.dict.js'
var log = console.log
function bind(fn) { function bind(fn) {
document.addEventListener('keydown', fn, false) document.addEventListener('keydown', fn, false)
return fn return fn
@ -17,47 +15,102 @@ function unbind(fn) {
document.removeEventListener('keydown', fn, false) document.removeEventListener('keydown', fn, false)
} }
function hide(target, name, value) {
Object.defineProperty(target, name, {
value,
writable: true,
enumerable: false,
configurable: true
})
}
// 按键检测
function check(ev) {
var checked = false
checked = ev.keyCode === this.keys.key
if (checked) {
for (let k of this.keys) {
checked = ev[`${k}Key`]
}
}
return checked
}
export default class Keyboard { export default class Keyboard {
constructor() { constructor() {
this.__EVENTS__ = [] hide(this, '__EVENTS__', {})
this.__LAST__ = {}
this.__TIME__ = 0
this._keydown = bind(ev => { hide(
if (MULTI_KEY_CODES.includes(ev.keyCode)) { this,
return '_keydown',
} bind(ev => {
for (let k in this.__EVENTS__) { if (MULTI_KEY_CODES.includes(ev.keyCode)) {
var item = this.__EVENTS__[k] return
var res = item.check(ev)
if (res) {
this.__LAST__[k]++
} else {
this.__LAST__[k] = -1
} }
log('-------------------', res, this.__LAST__[k])
if (this.__LAST__[k] + 1 === item.dict.length) { for (let k in this.__EVENTS__) {
item.fn.forEach(function(fn) { var item = this.__EVENTS__[k]
fn(ev) var res = item.check(ev)
}) var now = Date.now()
break var end = false // 是否结束检测
// 假设之前有激活过, 优先再检测子组合
if (item.actived === true) {
// 如果超时(300毫秒)了, 则不再管子组合了
if (now - item.last > 300) {
delete item.actived
} else {
for (let next of item.next) {
let tmp = next.check(ev)
if (tmp) {
end = true
// 第2组一定有回调,无需判断
next.fn.forEach(function(fn) {
fn(ev)
})
break
}
}
}
}
// 第一组, 只要检测通过了, 就触发回调
if (res) {
end = true
if (item.next) {
item.actived = true
item.last = now
}
// 有回调就触发
if (item.fn) {
item.fn.forEach(function(fn) {
fn(ev)
})
}
}
// 命中了, 后面的就不再检测了
if (end) {
break
}
} }
} })
}) )
} }
destroy() { destroy() {
delete this.__EVENTS__
unbind(this._keydown) unbind(this._keydown)
delete this.__EVENTS__
delete this._keydown
} }
on(act, callback) { __parse_action__(act) {
var key = [] var keys = []
var dict = [] var dict = []
var passed = true // 语法检测通过 var passed = true // 语法检测通过
var _this = this
act.forEach(it => { act.forEach(it => {
var tmp = [] var tmp = []
@ -81,44 +134,126 @@ export default class Keyboard {
} }
return k return k
}) })
key.push(it.join('+')) keys.push(it.join('+'))
}) })
return { keys, dict, passed }
}
off(act, callback) {
if (act.length < 1 || typeof callback !== 'function') {
return console.error('无效热键或回调')
}
let { keys, dict, passed } = this.__parse_action__(act)
if (passed) { if (passed) {
key = key.join(',') let k1 = keys[0]
this.__LAST__[key] = -1
if (this.__EVENTS__[key]) { if (keys.length === 1) {
this.__EVENTS__[key].fn.push(callback) if (this.__EVENTS__[k1] && this.__EVENTS__[k1].fn) {
} else { let i = 0
this.__EVENTS__[key] = { for (let fn of this.__EVENTS__[k1].fn) {
key, if (fn === callback) {
dict, this.__EVENTS__[k1].fn.splice(i, 1)
check(ev) { // 只剩1个, 删除fn字段
var now = Date.now() if (this.__EVENTS__[k1].fn.length < 1) {
var idx = _this.__LAST__[this.key] delete this.__EVENTS__[k1].fn
var action = this.dict[idx + 1]
var checked = false
if (action) { // 删完fn字段, 如果没有next字段的话, 整个热键删除
checked = ev.keyCode === action.key if (!this.__EVENTS__[k1].next) {
delete this.__EVENTS__[k1]
if (idx > -1) {
if (now - _this.__TIME__ > 100) {
checked = false
} }
} }
if (checked) { break
for (let k of action) {
checked = ev[`${k}Key`]
}
}
log(action, now, checked)
} }
i++
}
}
} else {
if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) {
let k2 = keys[1]
let i = 0
let sub
return checked for (sub of this.__EVENTS__[k1].next) {
}, if (sub.id === k2) {
fn: [callback] break
}
i++
}
if (sub.fn) {
let j = 0
for (let fn of sub.fn) {
if (fn === callback) {
sub.fn.splice(j, 1)
// 如果该组的事件为空了, 则删除该组合
if (sub.fn.length < 1) {
this.__EVENTS__[k1].next.splice(i, 1)
// 第2组, 如果回调队列为空了, 直接删除整个第2组
if (this.__EVENTS__[k1].next.length < 1) {
delete this.__EVENTS__[k1].next
// 如果此时, 上一层没的回调了, 则整个热键删除
if (!this.__EVENTS__[k1].fn) {
delete this.__EVENTS__[k1]
}
}
}
}
j++
}
}
}
}
}
}
on(act, callback) {
if (act.length < 1 || typeof callback !== 'function') {
return console.error('无效热键或回调')
}
let { keys, dict, passed } = this.__parse_action__(act)
if (passed) {
// 最多支持2组
let first = keys.shift()
let second = keys.shift()
if (!this.__EVENTS__[first]) {
this.__EVENTS__[first] = {
id: first,
keys: dict[0],
last: 0, //上次触发时间戳
check
}
}
if (second) {
if (this.__EVENTS__[first].next) {
for (let i = -1, it; (it = this.__EVENTS__[first].next[++i]); ) {
if (it.id === second) {
it.fn.push(callback)
return
}
}
}
this.__EVENTS__[first].next = [
{
id: second,
keys: dict[1],
check,
fn: [callback]
}
]
} else {
if (this.__EVENTS__[first].fn) {
this.__EVENTS__[first].fn.push(callback)
} else {
this.__EVENTS__[first].fn = [callback]
} }
} }
} }

View File

@ -69,8 +69,8 @@ export const KEY_DICT = {
backspace: 8, backspace: 8,
numlock: 12, numlock: 12,
capslock: 20, capslock: 20,
escape: 27, esc: 27,
contextmenu: 93, menu: 93,
insert: 45, insert: 45,
delete: 46, delete: 46,
pagedown: 34, pagedown: 34,
@ -95,5 +95,8 @@ export const KEY_DICT = {
f12: 123, f12: 123,
f13: 124, // Print键 f13: 124, // Print键
f14: 125, // Screen键 f14: 125, // Screen键
f15: 126 // Pause键 f15: 126, // Pause键
print: 124, // Print键
screen: 125, // Screen键
pause: 126 // Pause键
} }