1. 背景
常用的后台管理,需要快捷键进行快捷操作,提高用户的使用便利。接下来说明如何实现快捷键功能,需要支持的功能有:
- 支持单个和批量注册或者取消快捷键
- 支持组合键(如 Ctrl+S, Ctrl+Shift+S 等)
- 支持 Mac 和 Windows 按键映射
- 支持事件阻止(preventDefault)和事件冒泡阻止(stopPropagation)
- 支持通配符删除所有快捷键
2. 整体实现
2.1 实现原理
通过监听 keydown 事件,与注册的事件进行对比,如果存在则执行。
完整流程:
2.2 代码实现
2.2.1 注册
js
const shortcuts = []
register(key, command, options = { prevent: true, stop: true }) {
const shortcut = { ...this.parseKey(key), command, ...options };
this.shortcuts.push(shortcut);
return shortcut;
}
// 解析修饰键
parseKey(keyString) {
const keys = keyString.toLowerCase().split("+");
return {
key: keys[keys.length - 1],
ctrlKey: keys.includes("ctrl"),
metaKey: keys.includes("meta"),
shiftKey: keys.includes("shift"),
altKey: keys.includes("alt"),
};
}
注意: 这样定义是为了识别组合键,方便后期进行对比,例如:ctrl+s,在键盘事件event对象中是key为's',修饰键盘ctrl为true,
json
ctrlKey: true
altKey: false
metaKey: false
shiftKey: false
code: "KeyS"
key: "s"
keyCode: 83
对象数据结构如下:
js
{
key: string, // 主键
ctrlKey: boolean, // 是否按下 Ctrl
metaKey: boolean, // 是否按下 Command
shiftKey: boolean, // 是否按下 Shift
altKey: boolean, // 是否按下 Alt
command: Function, // 回调函数
prevent: boolean, // 是否阻止默认行为
stop: boolean // 是否阻止事件冒泡
}
2.2.2 监听事件
使用keydown事件进行监听按键,将按键与已注册进行对比,如果有则进行执行
js
window.addEventListener("keydown", (e) => {
const matchingShortcuts = this.findMatchingShortcut(e);
matchingShortcuts.forEach((shortcut) => {
shortcut.prevent && e.preventDefault();
shortcut.stop && e.stopPropagation();
shortcut.command(e);
});
});
this.modifierKeys = ['ctrlKey', 'metaKey', 'shiftKey', 'altKey'];
// 匹配按键
findMatchingShortcut(event) {
return this.shortcuts.filter(shortcut => {
const preciseMatching = this.modifierKeys.every(key => shortcut[key] === event[key]) &&
shortcut.key === this.convertMacKey(event.key).toLowerCase();
const anyMatching = shortcut.key === '*';
return preciseMatching || anyMatching;
});
}
// mac中按键进行转换
convertMacKey(key) {
return (
{
[this.KEYBOARD.CONTROL]: this.KEYBOARD.CTRL,
[this.KEYBOARD.META]: this.KEYBOARD.COMMAND,
}[key] || key
);
}
2.2.3 取消
- 如果通配符删除所有
- 如果只有key则只匹配key
- 有key和command则两者进行匹配
js
cancel(key, command) {
if (key === '*') {
this.shortcuts.length = 0;
console.log('打印***shortcuts', this.shortcuts);
return;
}
const normalShortcut = this.parseKey(key);
const matchingShortcuts = this.findMatchingShortcut(normalShortcut);
for (let i = this.shortcuts.length - 1; i >= 0; i--) {
if (matchingShortcuts.includes(this.shortcuts[i]) &&
(!command || this.shortcuts[i].command === command)) {
this.shortcuts.splice(i, 1);
}
}
}
2.2.4 添加一个批量注册和取消
批量注册需要以对象形式进行注册或者取消
javascript
batchCancel(array) {
if (Array.isArray(array)) {
array.forEach(shortcut => this.cancel(shortcut.key, shortcut.command));
}
}
batchRegister(array) {
if (Array.isArray(array)) {
array.forEach(shortcut => this.register(shortcut.key, shortcut.command, shortcut.options));
}
}
3. 使用案例
3.1 初始化
ini
const shortcutManager = new ShortcutManager();
3.2 注册快捷键
3.2.1 单个注册
javascript
// 方式一:使用参数形式
shortcutManager.register(
"ctrl+s",
(e) => {
console.log('保存操作');
},
{ prevent: true, stop: true } // 可选参数
);
3.2.2 批量注册
javascript
shortcutManager.batchRegister([
{
key: "ctrl+shift+s",
command: (e) => {
console.log('另存为操作');
}
},
{
key: "esc",
command: (e) => {
console.log('退出操作');
}
}
]);
3.3 取消快捷键
3.3.1 取消单个快捷键
arduino
// 取消特定快捷键
shortcutManager.cancel('ctrl+s');
// 取消所有快捷键
shortcutManager.cancel('*');
3.3.2 批量取消快捷键
php
shortcutManager.batchCancel([
{ key: 'ctrl+s' },
{ key: 'esc' }
]);
3.4. 获取当前注册的所有快捷键
ini
const shortcuts = shortcutManager.getShortcuts();
4. 配置选项
4.1 register 方法参数
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
key | string | - | 快捷键组合,如 "ctrl+s" |
command | function | - | 快捷键触发时执行的函数 |
prevent | boolean | true | 是否阻止默认行为 |
stop | boolean | true | 是否阻止事件冒泡 |
4.2 注意事项
- 快捷键字符串不区分大小写,如 "ctrl+s" 和 "CTRL+S" 等效
- 组合键使用 "+" 连接,如 "ctrl+shift+s"
- 注册快捷键时,建议使用箭头函数来避免立即执行:
c
// 正确方式
shortcutManager.register('ctrl+s', () => saveFunction('param'));
// 错误方式(会立即执行)
shortcutManager.register('ctrl+s', saveFunction('param'));
5. 完整代码
kotlin
class ShortcutManager {
constructor() {
this.shortcuts = [];
this.modifierKeys = ['ctrlKey', 'metaKey', 'shiftKey', 'altKey'];
this.isInitialized = false;
this.KEYBOARD = {
CTRL: "Ctrl",
CONTROL: "Control",
META: "Meta",
COMMAND: "Command",
};
this.initialize();
}
initialize() {
if (this.isInitialized) return;
window.addEventListener("keydown", (e) => {
const matchingShortcuts = this.findMatchingShortcut(e);
matchingShortcuts.forEach((shortcut) => {
shortcut.prevent && e.preventDefault();
shortcut.stop && e.stopPropagation();
shortcut.command(e);
});
});
this.isInitialized = true;
console.log('快捷键管理器已初始化');
}
findMatchingShortcut(event) {
return this.shortcuts.filter(shortcut => {
const preciseMatching = this.modifierKeys.every(key => shortcut[key] === event[key]) &&
shortcut.key === this.convertMacKey(event.key).toLowerCase();
const anyMatching = shortcut.key === '*';
return preciseMatching || anyMatching;
});
}
convertMacKey(key) {
return (
{
[this.KEYBOARD.CONTROL]: this.KEYBOARD.CTRL,
[this.KEYBOARD.META]: this.KEYBOARD.COMMAND,
}[key] || key
);
}
parseKey(keyString) {
const keys = keyString.toLowerCase().split("+");
return {
key: keys[keys.length - 1],
ctrlKey: keys.includes("ctrl"),
metaKey: keys.includes("meta"),
shiftKey: keys.includes("shift"),
altKey: keys.includes("alt"),
};
}
register(key, command, options = { prevent: true, stop: true }) {
const shortcut = { ...this.parseKey(key), command, ...options };
this.shortcuts.push(shortcut);
return shortcut;
}
batchRegister(array) {
if (Array.isArray(array)) {
array.forEach(shortcut => this.register(shortcut.key, shortcut.command, shortcut.options));
}
}
cancel(key, command) {
if (key === '*') {
this.shortcuts.length = 0;
return;
}
const normalShortcut = this.parseKey(key);
const matchingShortcuts = this.findMatchingShortcut(normalShortcut);
for (let i = this.shortcuts.length - 1; i >= 0; i--) {
if (matchingShortcuts.includes(this.shortcuts[i]) &&
(!command || this.shortcuts[i].command === command)) {
this.shortcuts.splice(i, 1);
}
}
}
batchCancel(array) {
if (Array.isArray(array)) {
array.forEach(shortcut => this.cancel(shortcut.key, shortcut.command));
}
}
getShortcuts() {
return [...this.shortcuts];
}
}
6. 总结
最后总结一下整体流程:先注册存储:解析组合键(如ctrl+s
)为结构体存入数组,监听keydown
事件,捕获按键组合,对比事件对象与注册项的修饰键/主键,根据配置阻止默认行为/冒泡,触发回调 。 如有错误,请指正!