完成键盘的开发
parent
d6ca6d7133
commit
6c907b4e6d
93
Readme.md
93
Readme.md
|
@ -1,16 +1,40 @@
|
|||
## JS键盘热键
|
||||
> 支持各种按钮组合。
|
||||
> 支持各种按钮组合。原生js开发, 无任何依赖(不到`2KB`)。使用也非常简单。
|
||||
>> 1.0版 功能键(Shift, Ctrl, Alt/Option, Win/Cmd)不区分左右。
|
||||
|
||||
|
||||
![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个。
|
||||
|
||||
## 普通按键
|
||||
|
||||
|
||||
### 普通按键
|
||||
> 即除了辅助功能键以外的其他按键。可以单独设置, 也可以配合辅助按键组合使用。
|
||||
>> 但是, 不允许在一组里出现多次。如需要, 请分组。
|
||||
|
||||
|
@ -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上面的键) |
|
||||
| \ | \ | 反斜杠 |
|
227
src/index.es7
227
src/index.es7
|
@ -6,8 +6,6 @@
|
|||
|
||||
import { KEY_DICT, MULTI_KEYS, MULTI_KEY_CODES } from './key.dict.js'
|
||||
|
||||
var log = console.log
|
||||
|
||||
function bind(fn) {
|
||||
document.addEventListener('keydown', fn, false)
|
||||
return fn
|
||||
|
@ -17,47 +15,102 @@ function unbind(fn) {
|
|||
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 {
|
||||
constructor() {
|
||||
this.__EVENTS__ = []
|
||||
this.__LAST__ = {}
|
||||
this.__TIME__ = 0
|
||||
hide(this, '__EVENTS__', {})
|
||||
|
||||
this._keydown = bind(ev => {
|
||||
hide(
|
||||
this,
|
||||
'_keydown',
|
||||
bind(ev => {
|
||||
if (MULTI_KEY_CODES.includes(ev.keyCode)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let k in this.__EVENTS__) {
|
||||
var item = this.__EVENTS__[k]
|
||||
var res = item.check(ev)
|
||||
var now = Date.now()
|
||||
var end = false // 是否结束检测
|
||||
|
||||
if (res) {
|
||||
this.__LAST__[k]++
|
||||
// 假设之前有激活过, 优先再检测子组合
|
||||
if (item.actived === true) {
|
||||
// 如果超时(300毫秒)了, 则不再管子组合了
|
||||
if (now - item.last > 300) {
|
||||
delete item.actived
|
||||
} else {
|
||||
this.__LAST__[k] = -1
|
||||
}
|
||||
log('-------------------', res, this.__LAST__[k])
|
||||
|
||||
if (this.__LAST__[k] + 1 === item.dict.length) {
|
||||
item.fn.forEach(function(fn) {
|
||||
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() {
|
||||
delete this.__EVENTS__
|
||||
unbind(this._keydown)
|
||||
delete this.__EVENTS__
|
||||
delete this._keydown
|
||||
}
|
||||
|
||||
on(act, callback) {
|
||||
var key = []
|
||||
__parse_action__(act) {
|
||||
var keys = []
|
||||
var dict = []
|
||||
var passed = true // 语法检测通过
|
||||
var _this = this
|
||||
|
||||
act.forEach(it => {
|
||||
var tmp = []
|
||||
|
@ -81,45 +134,127 @@ export default class Keyboard {
|
|||
}
|
||||
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) {
|
||||
key = key.join(',')
|
||||
this.__LAST__[key] = -1
|
||||
let k1 = keys[0]
|
||||
|
||||
if (this.__EVENTS__[key]) {
|
||||
this.__EVENTS__[key].fn.push(callback)
|
||||
if (keys.length === 1) {
|
||||
if (this.__EVENTS__[k1] && this.__EVENTS__[k1].fn) {
|
||||
let i = 0
|
||||
for (let fn of this.__EVENTS__[k1].fn) {
|
||||
if (fn === callback) {
|
||||
this.__EVENTS__[k1].fn.splice(i, 1)
|
||||
// 只剩1个, 删除fn字段
|
||||
if (this.__EVENTS__[k1].fn.length < 1) {
|
||||
delete this.__EVENTS__[k1].fn
|
||||
|
||||
// 删完fn字段, 如果没有next字段的话, 整个热键删除
|
||||
if (!this.__EVENTS__[k1].next) {
|
||||
delete this.__EVENTS__[k1]
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.__EVENTS__[key] = {
|
||||
key,
|
||||
dict,
|
||||
check(ev) {
|
||||
var now = Date.now()
|
||||
var idx = _this.__LAST__[this.key]
|
||||
var action = this.dict[idx + 1]
|
||||
var checked = false
|
||||
if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) {
|
||||
let k2 = keys[1]
|
||||
let i = 0
|
||||
let sub
|
||||
|
||||
if (action) {
|
||||
checked = ev.keyCode === action.key
|
||||
|
||||
if (idx > -1) {
|
||||
if (now - _this.__TIME__ > 100) {
|
||||
checked = false
|
||||
for (sub of this.__EVENTS__[k1].next) {
|
||||
if (sub.id === k2) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (checked) {
|
||||
for (let k of action) {
|
||||
checked = ev[`${k}Key`]
|
||||
}
|
||||
}
|
||||
log(action, now, checked)
|
||||
i++
|
||||
}
|
||||
|
||||
return checked
|
||||
},
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,8 +69,8 @@ export const KEY_DICT = {
|
|||
backspace: 8,
|
||||
numlock: 12,
|
||||
capslock: 20,
|
||||
escape: 27,
|
||||
contextmenu: 93,
|
||||
esc: 27,
|
||||
menu: 93,
|
||||
insert: 45,
|
||||
delete: 46,
|
||||
pagedown: 34,
|
||||
|
@ -95,5 +95,8 @@ export const KEY_DICT = {
|
|||
f12: 123,
|
||||
f13: 124, // Print键
|
||||
f14: 125, // Screen键
|
||||
f15: 126 // Pause键
|
||||
f15: 126, // Pause键
|
||||
print: 124, // Print键
|
||||
screen: 125, // Screen键
|
||||
pause: 126 // Pause键
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue