diff --git a/Readme.md b/Readme.md index abd64cf..7dceb6b 100644 --- a/Readme.md +++ b/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, callback) +> 监听键盘动作组合, 支持单组或双组。 +>> 双组时, 2组按键前后时差不能超过`300毫秒`, 否则视为2次独立的操作。 +>> **键名不区分大小写** + +--- + ++ .off(actions, callback) +> 移除键盘监听。 + +--- + ++ .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上面的键) | +| \ | \ | 反斜杠 | \ No newline at end of file diff --git a/src/index.es7 b/src/index.es7 index 2b654e2..15e4b7d 100644 --- a/src/index.es7 +++ b/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 => { - if (MULTI_KEY_CODES.includes(ev.keyCode)) { - return - } - for (let k in this.__EVENTS__) { - var item = this.__EVENTS__[k] - var res = item.check(ev) - - if (res) { - this.__LAST__[k]++ - } else { - this.__LAST__[k] = -1 + hide( + this, + '_keydown', + bind(ev => { + if (MULTI_KEY_CODES.includes(ev.keyCode)) { + return } - log('-------------------', res, this.__LAST__[k]) - if (this.__LAST__[k] + 1 === item.dict.length) { - item.fn.forEach(function(fn) { - fn(ev) - }) - break + for (let k in this.__EVENTS__) { + var item = this.__EVENTS__[k] + var res = item.check(ev) + var now = Date.now() + 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() { - 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,44 +134,126 @@ 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) - } 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 (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 - if (action) { - checked = ev.keyCode === action.key - - if (idx > -1) { - if (now - _this.__TIME__ > 100) { - checked = false + // 删完fn字段, 如果没有next字段的话, 整个热键删除 + if (!this.__EVENTS__[k1].next) { + delete this.__EVENTS__[k1] } } - if (checked) { - for (let k of action) { - checked = ev[`${k}Key`] - } - } - log(action, now, checked) + break } + i++ + } + } + } else { + if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) { + let k2 = keys[1] + let i = 0 + let sub - return checked - }, - fn: [callback] + for (sub of this.__EVENTS__[k1].next) { + if (sub.id === k2) { + 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] } } } diff --git a/src/key.dict.es7 b/src/key.dict.es7 index 69a4262..57ac83d 100644 --- a/src/key.dict.es7 +++ b/src/key.dict.es7 @@ -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键 }