在低代码系统中,快捷键是很常用的操作,我们如果只对绘图区域注册快捷键,那么焦点不在绘图区域时,就会失去快捷键响应,如果对全局拦截键盘事件,注册快捷键,那么会失去一些本应该交给系统默认快捷键的行为,例如:文本框选后的复制、文本输入框的粘贴等。
所以,设计一个既不与全局快捷键冲突,又可以全局监听的快捷键方案。
适用于 vue ,但是通过简单改造可适用于大部分前端技术栈。
一、定义一个 HotKeyManager 类,在 utils 中 新建 hotkey-manager.ts 文件:
interface IKey {ctrlKey?: booleanshiftKey?: booleanaltKey?: booleanmetaKey?: booleankeyCode: string
}
export interface IKeyBinding {key: IKey | IKey[]action: (...arg) => void
}export class HotKeyManager {keyEvents: IKeyBinding[]constructor(keyEvents: IKeyBinding[]) {this.keyEvents = keyEventsthis.init()}private init() {document.addEventListener('keydown', this.handleAction)}private handleAction = (e: KeyboardEvent) => {// 1、如果在输入框中,不处理。2、如果在外部选中了文字不处理。3、如果元素的 contenteditable 属性为 true ,不处理const selection = window.getSelection()?.toString()if (e.target instanceof HTMLInputElement ||e.target instanceof HTMLTextAreaElement ||(e.target && (e.target as HTMLElement).contentEditable === 'true') ||selection) {return}for (const keyBinding of this.keyEvents) {if (this.isKeyMatch(keyBinding.key, e)) {e.preventDefault() // 如果匹配到快捷键,阻止默认事件keyBinding.action(e)return}}}private isKeyMatch(key: IKey | IKey[], e: KeyboardEvent): boolean {if (Array.isArray(key)) {return key.some((k) => this.isKeyMatch(k, e))}if (key.keyCode === '*') return trueconst {ctrlKey = false,shiftKey = false,altKey = false,metaKey = false,} = keyreturn (ctrlKey === e.ctrlKey &&shiftKey === e.shiftKey &&altKey === e.altKey &&metaKey === e.metaKey &&key.keyCode === e.code)}destroy() {this.keyEvents = []document.removeEventListener('keydown', this.handleAction)}
}
二、注册当前系统使用到的快捷键,因为我这里的命令使用到 pinia 中的方法,所以我定义了一个 hooks,在 hooks 文件夹下新建 hotkey-list.ts 文件:
import type { IKeyBinding } from '@/utils/hotkey-manager'
import globalStore from '@/store/global'
import copyManager from '@/store/copy'
import snapshot from '@/store/snapshot'const { moveShape } = globalStore()export default () => {const keyList: IKeyBinding[] = [{key: [{ keyCode: 'Backspace' }, { keyCode: 'Delete' }],action: () => {// 删除const { deleteComponent } = globalStore()deleteComponent()},},{key: { ctrlKey: true, keyCode: 'KeyC' },action: () => {// 复制当前选中的部件const { copy } = copyManager()copy()},},{key: { ctrlKey: true, keyCode: 'KeyX' },action: () => {// 剪切const { cut } = copyManager()cut()},},{key: { ctrlKey: true, keyCode: 'KeyV' },action: () => {// 粘贴const { paste } = copyManager()paste()},},{key: { ctrlKey: true, keyCode: 'KeyZ' },action: () => {// 撤销const { undo } = snapshot()undo()},},{key: [{ ctrlKey: true, shiftKey: true, keyCode: 'KeyZ' },{ ctrlKey: true, keyCode: 'KeyY' },],action: () => {// 恢复const { redo } = snapshot()redo()},},{key: { ctrlKey: true, keyCode: 'KeyP' },action: () => {// 预览},},{key: { ctrlKey: true, keyCode: 'KeyS' },action: () => {// 保存},},{key: { keyCode: 'ArrowUp' },action: () => {// 上移moveShape({ x: 0, y: -1 })},},{key: { keyCode: 'ArrowUp', shiftKey: true },action: () => {// 上移10pxmoveShape({ x: 0, y: -10 })},},{key: { keyCode: 'ArrowDown' },action: () => {// 下移moveShape({ x: 0, y: 1 })},},{key: { keyCode: 'ArrowDown', shiftKey: true },action: () => {// 下移10pxmoveShape({ x: 0, y: 10 })},},{key: { keyCode: 'ArrowLeft' },action: () => {// 左移moveShape({ x: -1, y: 0 })},},{key: { keyCode: 'ArrowLeft', shiftKey: true },action: () => {// 左移10pxmoveShape({ x: -10, y: 0 })},},{key: { keyCode: 'ArrowRight' },action: () => {// 右移moveShape({ x: 1, y: 0 })},},{key: { keyCode: 'ArrowRight', shiftKey: true },action: () => {// 右移10pxmoveShape({ x: 10, y: 0 })},},]return { keyList }
}
三、实例化使用
import { HotKeyManager } from '@/utils/hotkey-manager'
import useHotKeyList from '@/hooks/hotkey-list'const { keyList } = useHotKeyList()const hotKeyManager = new HotKeyManager(keyList)
如果觉得 hooks 的方式不太方便,也可以改造一下 HotKeyManager 类,通过在 HotKeyManager 实例上注册回调函数的形式获取当前触发的快捷键,然后在实例化的地方执行某些动作。