From 6556d213c10e9227dd5ca0ed5e6bcda1a75e78e6 Mon Sep 17 00:00:00 2001 From: tujiawei Date: Mon, 9 Oct 2023 19:40:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=201.=E7=89=88=E6=9C=AC=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Keyboard.v1.js | 228 ++++++++++++++++++++ src/Keyboard.v2.js | 228 ++++++++++++++++++++ src/index.es7 | 302 --------------------------- src/index.js | 8 + src/{key.dict.es7 => key.dict.v1.js} | 0 src/key.dict.v2.js | 112 ++++++++++ src/utils/bind.js | 11 + src/utils/check.js | 23 ++ src/utils/hide.js | 10 + src/utils/index.js | 4 + src/utils/parseAction.js | 43 ++++ 11 files changed, 667 insertions(+), 302 deletions(-) create mode 100644 src/Keyboard.v1.js create mode 100644 src/Keyboard.v2.js delete mode 100644 src/index.es7 create mode 100644 src/index.js rename src/{key.dict.es7 => key.dict.v1.js} (100%) create mode 100644 src/key.dict.v2.js create mode 100644 src/utils/bind.js create mode 100644 src/utils/check.js create mode 100644 src/utils/hide.js create mode 100644 src/utils/index.js create mode 100644 src/utils/parseAction.js diff --git a/src/Keyboard.v1.js b/src/Keyboard.v1.js new file mode 100644 index 0000000..b3f7147 --- /dev/null +++ b/src/Keyboard.v1.js @@ -0,0 +1,228 @@ +/** + * + * @author yutent + * @date 2020/10/29 16:48:26 + */ + +import { MULTI_KEY_CODES } from './key.dict.v1.js' +import { bind, unbind, hide, check, parseAction } from './utils'; + +class KeyboardV1 { + constructor(elem) { + this.$elem = elem + + hide(this, '__EVENTS__', {}) + hide(this, 'paused', false) + hide(this, '__finally__', null) + + hide( + this, + '_keydown', + bind(elem, ev => { + // 允许暂停 + if (this.paused) { + return + } + + // 屏蔽纯辅助键的监听 + 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 (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 + } + } + + if (this.__finally__) { + this.__finally__(ev) + } + }) + ) + } + + get disabled() { + return this.paused + } + + set disabled(v) { + this.paused = !!v + } + + destroy() { + unbind(this.$elem, this._keydown) + delete this.__EVENTS__ + delete this._keydown + } + + + + off(act, callback) { + if (act.length < 1 || typeof callback !== 'function') { + return console.error('无效热键或回调') + } + + let { keys, dict, passed } = parseAction(act) + + if (passed) { + let k1 = keys[0] + + 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 { + if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) { + let k2 = keys[1] + let i = 0 + let sub + + 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 } = parseAction(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] + } + } + } + } + + finally(callback) { + if (typeof callback === 'function') { + this.__finally__ = callback + } + } +} + +export { KeyboardV1 }; diff --git a/src/Keyboard.v2.js b/src/Keyboard.v2.js new file mode 100644 index 0000000..401544d --- /dev/null +++ b/src/Keyboard.v2.js @@ -0,0 +1,228 @@ +/** + * + * @author yutent + * @date 2020/10/29 16:48:26 + */ + +import { MULTI_KEY_CODES } from './key.dict.v2.js' +import { bind, unbind, hide, check, parseAction } from './utils'; + +class KeyboardV2 { + constructor(elem) { + this.$elem = elem + + hide(this, '__EVENTS__', {}) + hide(this, 'paused', false) + hide(this, '__finally__', null) + + hide( + this, + '_keydown', + bind(elem, ev => { + // 允许暂停 + if (this.paused) { + return + } + + // 屏蔽纯辅助键的监听 + 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 (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 + } + } + + if (this.__finally__) { + this.__finally__(ev) + } + }) + ) + } + + get disabled() { + return this.paused + } + + set disabled(v) { + this.paused = !!v + } + + destroy() { + unbind(this.$elem, this._keydown) + delete this.__EVENTS__ + delete this._keydown + } + + + + off(act, callback) { + if (act.length < 1 || typeof callback !== 'function') { + return console.error('无效热键或回调') + } + + let { keys, dict, passed } = parseAction(act) + + if (passed) { + let k1 = keys[0] + + 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 { + if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) { + let k2 = keys[1] + let i = 0 + let sub + + 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 } = parseAction(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] + } + } + } + } + + finally(callback) { + if (typeof callback === 'function') { + this.__finally__ = callback + } + } +} + +export { KeyboardV2 }; diff --git a/src/index.es7 b/src/index.es7 deleted file mode 100644 index bee0bf2..0000000 --- a/src/index.es7 +++ /dev/null @@ -1,302 +0,0 @@ -/** - * - * @author yutent - * @date 2020/10/29 16:48:26 - */ - -import { KEY_DICT, MULTI_KEYS, MULTI_KEY_CODES } from './key.dict.js' - -function bind(el, fn) { - ;(el || document).addEventListener('keydown', fn, false) - return fn -} - -function unbind(el, fn) { - ;(el || 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 - - if (typeof this.keys.key === 'object') { - checked = this.keys.key.includes(ev.keyCode) - } else { - checked = ev.keyCode === this.keys.key - } - - if (checked) { - for (let k in this.keys) { - if (ev[k] !== this.keys[k]) { - checked = false - break - } - } - } - - return checked -} - -export default class Keyboard { - constructor(elem) { - this.$elem = elem - - hide(this, '__EVENTS__', {}) - hide(this, 'paused', false) - hide(this, '__finally__', null) - - hide( - this, - '_keydown', - bind(elem, ev => { - // 允许暂停 - if (this.paused) { - return - } - - // 屏蔽纯辅助键的监听 - 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 (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 - } - } - - if (this.__finally__) { - this.__finally__(ev) - } - }) - ) - } - - get disabled() { - return this.paused - } - - set disabled(v) { - this.paused = !!v - } - - destroy() { - unbind(this.$elem, this._keydown) - delete this.__EVENTS__ - delete this._keydown - } - - __parse_action__(act) { - var keys = [] - var dict = [] - var passed = true // 语法检测通过 - - act.forEach(it => { - var tmp = {} - - it = it.split('+').map(k => { - k = k.trim().toLowerCase() - if (MULTI_KEYS[k]) { - if (tmp[`${k}Key`]) { - passed = false - console.error('功能键,同组中不能重复。⎣%s⎤', it) - } else { - tmp[`${k}Key`] = true - } - } else { - if (tmp.key) { - passed = false - console.error('非功能键,同组不能出现多个。⎣%s⎤', it) - } else { - hide(tmp, 'key', KEY_DICT[k]) - } - } - return k - }) - - for (let k in MULTI_KEYS) { - tmp[`${k}Key`] = tmp[`${k}Key`] || false - } - - dict.push(tmp) - 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) { - let k1 = keys[0] - - 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 { - if (this.__EVENTS__[k1] && this.__EVENTS__[k1].next) { - let k2 = keys[1] - let i = 0 - let sub - - 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] - } - } - } - } - - finally(callback) { - if (typeof callback === 'function') { - this.__finally__ = callback - } - } -} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..ec49f35 --- /dev/null +++ b/src/index.js @@ -0,0 +1,8 @@ +/** + * + * @author yutent + * @date 2020/10/29 16:48:26 + */ + +export { KeyboardV1 } from './Keyboard.v1'; +export { KeyboardV2 } from './Keyboard.v2'; diff --git a/src/key.dict.es7 b/src/key.dict.v1.js similarity index 100% rename from src/key.dict.es7 rename to src/key.dict.v1.js diff --git a/src/key.dict.v2.js b/src/key.dict.v2.js new file mode 100644 index 0000000..b404123 --- /dev/null +++ b/src/key.dict.v2.js @@ -0,0 +1,112 @@ +// 允许组合出现的辅助键 +export const MULTI_KEYS = { + ctrl: 1, + shift: 1, + alt: 1, + meta: 1 +} + +export const MULTI_KEY_CODES = [200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212] + +// 按键对应的code值 +export const KEY_DICT = { + '0': 48, // Digit{N}, Numpad{N} + '1': 49, + '2': 50, + '3': 51, + '4': 52, + '5': 53, + '6': 54, + '7': 55, + '8': 56, + '9': 57, + "'": 222, + '*': 106, + '+': 107, + ',': 188, + '-': [109, 189], + '.': [110, 190], + '/': [111, 191], + ';': 186, + '=': 187, + '[': 219, + '\\': 220, + ']': 221, + '`': 192, + a: 65, + b: 66, + c: 67, + d: 68, + e: 69, + f: 70, + g: 71, + h: 72, + i: 73, + j: 74, + k: 75, + l: 76, + m: 77, + n: 78, + o: 79, + p: 80, + q: 81, + r: 82, + s: 83, + t: 84, + u: 85, + v: 86, + w: 87, + x: 88, + y: 89, + z: 90, + enter: 13, // Enter, NumpadEnter + space: 32, + meta: 91, // MetaLeft, MetaRight + tab: 9, + backspace: 8, + numlock: 12, + capslock: 20, + esc: 27, + menu: 93, + insert: 45, + delete: 46, + pagedown: 34, + pageup: 33, + home: 36, + end: 35, + up: 38, + down: 40, + left: 37, + right: 39, + f1: 112, + f2: 113, + f3: 114, + f4: 115, + f5: 116, + f6: 117, + f7: 118, + f8: 119, + f9: 120, + f10: 121, + f11: 122, + f12: 123, + f13: 124, // Print键 + f14: 125, // Screen键 + f15: 126, // Pause键 + print: 124, // Print键 + screen: 125, // Screen键 + pause: 126, // Pause键 + + shift_left: 200, // ShiftLeft + shift_right: 201, // ShiftRight + ctrl_left: 202, // ControlLeft + ctrl_right: 203, // ControlRight + alt_left: 204, // AltLeft + alt_right: 205, // AltRight + option_left: 207, // OptionLeft + option_right: 208, // OptionRight + win_left: 209, // WinLeft + win_right: 210, // WinRight + cmd_left: 211, // CmdRight + cmd_right: 212, // CmdRight +} diff --git a/src/utils/bind.js b/src/utils/bind.js new file mode 100644 index 0000000..4eaca01 --- /dev/null +++ b/src/utils/bind.js @@ -0,0 +1,11 @@ +function bind(el, fn) { + ;(el || document).addEventListener('keydown', fn, false) + + return fn +} + +function unbind(el, fn) { + ;(el || document).removeEventListener('keydown', fn, false) +} + +export { bind, unbind }; \ No newline at end of file diff --git a/src/utils/check.js b/src/utils/check.js new file mode 100644 index 0000000..595a9bd --- /dev/null +++ b/src/utils/check.js @@ -0,0 +1,23 @@ +// 按键检测 +function check(ev) { + let checked = false + + if (typeof this.keys.key === 'object') { + checked = this.keys.key.includes(ev.keyCode) + } else { + checked = ev.keyCode === this.keys.key + } + + if (checked) { + for (let k in this.keys) { + if (ev[k] !== this.keys[k]) { + checked = false + break + } + } + } + + return checked +} + +export { check }; \ No newline at end of file diff --git a/src/utils/hide.js b/src/utils/hide.js new file mode 100644 index 0000000..ef63b1a --- /dev/null +++ b/src/utils/hide.js @@ -0,0 +1,10 @@ +function hide(target, name, value) { + Object.defineProperty(target, name, { + value, + writable: true, + enumerable: false, + configurable: true + }) +} + +export { hide }; \ No newline at end of file diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..6091359 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,4 @@ +export { bind, unbind } from './bind'; +export { hide } from './hide'; +export { check } from './check'; +export { parseAction } from './parseAction'; \ No newline at end of file diff --git a/src/utils/parseAction.js b/src/utils/parseAction.js new file mode 100644 index 0000000..f80776e --- /dev/null +++ b/src/utils/parseAction.js @@ -0,0 +1,43 @@ +import {KEY_DICT, MULTI_KEYS} from "../key.dict.v1"; +import {hide} from "./hide"; + +function parseAction(act) { + var keys = [] + var dict = [] + var passed = true // 语法检测通过 + + act.forEach(it => { + var tmp = {} + + it = it.split('+').map(k => { + k = k.trim().toLowerCase() + if (MULTI_KEYS[k]) { + if (tmp[`${k}Key`]) { + passed = false + console.error('功能键,同组中不能重复。⎣%s⎤', it) + } else { + tmp[`${k}Key`] = true + } + } else { + if (tmp.key) { + passed = false + console.error('非功能键,同组不能出现多个。⎣%s⎤', it) + } else { + hide(tmp, 'key', KEY_DICT[k]) + } + } + return k + }) + + for (let k in MULTI_KEYS) { + tmp[`${k}Key`] = tmp[`${k}Key`] || false + } + + dict.push(tmp) + keys.push(it.join('+')) + }) + + return { keys, dict, passed } +} + +export { parseAction }; \ No newline at end of file