键盘事件

lishihuan大约 9 分钟

键盘事件

方案

第三方库

1. Mousetrap

在 Vue.js 项目中使用 Mousetrap 可以简化快捷键的管理和监听。Mousetrap 是一个轻量级的 JavaScript 库,专门用于处理键盘快捷键。它支持简单的快捷键绑定、组合键(如 Ctrl+C)、以及全局和局部的快捷键监听。

文档:https://mochajs.org/open in new window

安装 Mousetrap

首先,你需要安装 Mousetrap。你可以通过 npm 或 yarn 来安装:

npm install mousetrap --save

或者

yarn add mousetrap

在 Vue 中引入并使用 Mousetrap

接下来,你可以在你的 Vue 组件或全局文件中引入 Mousetrap 并开始使用它。

1. 全局引入

如果你希望在整个应用中都可以使用 Mousetrap,可以在 main.js 中全局引入:

import Vue from 'vue';
import App from './App.vue';
import Mousetrap from 'mousetrap';

Vue.config.productionTip = false;

// 将 Mousetrap 挂载到 Vue 的原型上,以便在所有组件中使用
Vue.prototype.$mousetrap = Mousetrap;

new Vue({
  render: h => h(App),
}).$mount('#app');

这样,你就可以在任何 Vue 组件中通过 this.$mousetrap 来访问 Mousetrap

2. 局部引入

如果你只需要在某个特定的组件中使用 Mousetrap,可以在该组件中局部引入:

<template>
  <div>
    <!-- 组件模板 -->
  </div>
</template>

<script>
import Mousetrap from 'mousetrap';

export default {
  name: 'YourComponentName',
  mounted() {
    // 绑定快捷键
    this.bindShortcuts();
  },
  beforeDestroy() {
    // 解绑快捷键
    this.unbindShortcuts();
  },
  methods: {
    bindShortcuts() {
      // 绑定 Ctrl+Shift+A 快捷键
      Mousetrap.bind('ctrl+shift+a', (e) => {
        e.preventDefault(); // 阻止默认行为
        console.log('Ctrl+Shift+A 被触发');
      });

      // 绑定多个快捷键
      Mousetrap.bind(['command+s', 'ctrl+s'], (e) => {
        e.preventDefault();
        console.log('保存文档');
      });

      // 绑定单个按键
      Mousetrap.bind('esc', () => {
        console.log('Esc 被按下');
      });
    },
    unbindShortcuts() {
      // 解绑所有快捷键
      Mousetrap.unbind('ctrl+shift+a');
      Mousetrap.unbind(['command+s', 'ctrl+s']);
      Mousetrap.unbind('esc');
    }
  }
};
</script>

3. 使用 Mousetrap 插件

为了更好地集成 Mousetrap,你可以创建一个 Vue 插件来封装 Mousetrap 的功能。这使得你可以更方便地在多个组件中使用 Mousetrap,并且可以集中管理快捷键的绑定和解绑。

创建插件:

// plugins/mousetrap.js
import Mousetrap from 'mousetrap';

const MousetrapPlugin = {
  install(Vue, options) {
    // 创建一个全局的 Mousetrap 实例
    const mousetrapInstance = new Mousetrap(document.body);

    // 定义一个方法来绑定快捷键
    Vue.prototype.$bindShortcut = (keys, callback, action = 'keydown') => {
      mousetrapInstance.bind(keys, callback, action);
    };

    // 定义一个方法来解绑快捷键
    Vue.prototype.$unbindShortcut = (keys, action = 'keydown') => {
      mousetrapInstance.unbind(keys, action);
    };

    // 提供一个方法来清除所有绑定
    Vue.prototype.$clearShortcuts = () => {
      mousetrapInstance.reset();
    };
  }
};

export default MousetrapPlugin;

注册插件:

// main.js
import Vue from 'vue';
import App from './App.vue';
import MousetrapPlugin from './plugins/mousetrap';

Vue.config.productionTip = false;

// 使用插件
Vue.use(MousetrapPlugin);

new Vue({
  render: h => h(App),
}).$mount('#app');

在组件中使用插件:

<template>
  <div>
    <!-- 组件模板 -->
  </div>
</template>

<script>
export default {
  name: 'YourComponentName',
  mounted() {
    // 绑定快捷键
    this.bindShortcuts();
  },
  beforeDestroy() {
    // 解绑快捷键
    this.unbindShortcuts();
  },
  methods: {
    bindShortcuts() {
      this.$bindShortcut('ctrl+shift+a', (e) => {
        e.preventDefault(); // 阻止默认行为
        console.log('Ctrl+Shift+A 被触发');
      });

      this.$bindShortcut(['command+s', 'ctrl+s'], (e) => {
        e.preventDefault();
        console.log('保存文档');
      });

      this.$bindShortcut('esc', () => {
        console.log('Esc 被按下');
      });
    },
    unbindShortcuts() {
      this.$unbindShortcut('ctrl+shift+a');
      this.$unbindShortcut(['command+s', 'ctrl+s']);
      this.$unbindShortcut('esc');
    }
  }
};
</script>

4. 处理全局与局部快捷键

有时你可能需要在不同的上下文中使用全局和局部的快捷键。Mousetrap 支持通过指定不同的 DOM 元素来绑定快捷键,从而实现局部快捷键的效果。

例如,你可以将 Mousetrap 实例绑定到某个特定的 DOM 元素上,而不是全局的 document.body

mounted() {
  // 绑定到当前组件的根元素
  this.mousetrapInstance = new Mousetrap(this.$el);

  // 绑定快捷键
  this.bindShortcuts();
},
beforeDestroy() {
  // 清除所有绑定
  this.mousetrapInstance.reset();
}

5. 处理冲突和优先级

如果多个组件绑定了相同的快捷键,可能会导致冲突。Mousetrap 默认会按照绑定的顺序来执行回调函数。你可以通过 stopCallback 方法来控制是否阻止某些快捷键的执行,从而避免冲突。

methods: {
  bindShortcuts() {
    this.$bindShortcut('ctrl+s', (e) => {
      e.preventDefault();
      console.log('保存文档');
    }, 'keydown');

    // 阻止其他组件的 Ctrl+S 快捷键
    Mousetrap.stopCallback = (e, element, combo) => {
      if (combo === 'ctrl+s' && element.tagName.toLowerCase() !== 'input') {
        return false; // 允许执行快捷键
      }
      return true; // 阻止执行快捷键
    };
  }
}

6. 使用 Mousetrap 的高级功能

Mousetrap 还提供了许多高级功能,例如:

  • 序列快捷键:支持按顺序输入多个按键,如 g i
  • 修饰键:支持 CtrlShiftAlt 等修饰键。
  • 多平台支持:自动处理不同操作系统上的快捷键差异(如 Command 键在 Mac 上)。

你可以查阅 Mousetrap 的官方文档open in new window 了解更多详细信息和用法。

总结

通过以上步骤,你可以在 Vue.js 项目中轻松集成 Mousetrap,并实现强大的键盘快捷键功能。无论是全局快捷键还是局部快捷键,Mousetrap 都能帮助你简化开发过程,并提供灵活的快捷键管理方式。

2. hotkeys

  • 导包
npm install hotkeys-js --save
# 或者
yarn add hotkeys-js

  • main.js中挂载全局
import hotkeys from 'hotkeys-js';
// 将 hotkeys 挂载到 Vue 实例上,方便全局使用
Vue.prototype.$hotkeys = hotkeys;
  • 使用
<template>
  <div>
    <h1>Vue Hotkeys 示例</h1>
    <p>按下 F1 或 Ctrl + S 来触发事件</p>
  </div>
</template>

<script>
export default {
  name: 'HotkeysExample',

  mounted() {
    // 使用全局快捷键
    this.$hotkeys('f1', () => {
      console.log('F1 被按下');
    });

    this.$hotkeys('ctrl+s', (event) => {
      event.preventDefault(); // 阻止默认行为
      console.log('Ctrl+S 被按下');
    });
  },

  beforeDestroy() {
    // 解除绑定的快捷键(可选)
    this.$hotkeys.unbind('f1');
    this.$hotkeys.unbind('ctrl+s');
  }
};
</script>


下面的方式目前没实现,有一定的可行性

在大型应用中,确实不应该让每个组件去单独处理全局的键盘事件。这样做会导致以下问题:

  1. 重复的事件监听器:如果每个组件都自己去监听键盘事件,可能会导致多个 keydownkeyup 事件监听器同时存在,浪费资源并且增加维护难度。

  2. 复杂的状态管理:如果多个组件对键盘事件进行独立处理,它们可能会互相影响,导致程序的行为不一致,尤其是在涉及键盘组合键时。

  3. 难以调试:当键盘事件的逻辑分散在不同的组件中,调试和追踪事件变得更加困难,因为你无法在一个地方看到所有的事件处理逻辑。

解决方案:统一的全局键盘事件管理

为了避免这些问题,建议你使用 全局事件管理 来处理键盘事件,类似于你提到的 WebSocket 的全局监听方式。你可以通过 VuexEvent Bus 或者 全局事件总线 来集中管理键盘事件。这样不仅能避免重复的事件监听,还能在系统中统一管理键盘事件的逻辑。

  • 方式1:通过 Vuex
  • 方式2:使用 Event Bus全局事件总线

方法 1:通过 Vuex 统一管理键盘事件

你可以通过 Vuex 来管理全局的键盘事件状态和处理逻辑。这样,任何需要监听键盘事件的组件都可以通过 Vuex 获取状态或者触发相应的行为,而无需自己绑定和移除事件监听器。

1. 创建一个 Vuex 模块来管理键盘事件

// store/modules/keyboard.js
export default {
  state: {
    pressedKeys: [], // 当前按下的键
  },
  mutations: {
    SET_KEY_DOWN(state, key) {
      if (!state.pressedKeys.includes(key)) {
        state.pressedKeys.push(key);
      }
    },
    SET_KEY_UP(state, key) {
      state.pressedKeys = state.pressedKeys.filter(k => k !== key);
    },
  },
  actions: {
    handleKeyDown({ commit }, key) {
      commit('SET_KEY_DOWN', key);
    },
    handleKeyUp({ commit }, key) {
      commit('SET_KEY_UP', key);
    },
  },
  getters: {
    getPressedKeys: (state) => state.pressedKeys,
  },
};

2. 在主 store 中注册模块

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import keyboard from './modules/keyboard';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    keyboard,
  },
});

3. 在主应用中监听键盘事件

App.vuemain.js 中统一监听键盘事件,并通过 Vuex 分发到各个组件:

// App.vue
<template>
  <div>
    <h1>Keyboard Event Handler</h1>
    <p>Pressed Keys: {{ pressedKeys.join(', ') }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters('keyboard', ['getPressedKeys']),
  },
  mounted() {
    window.addEventListener('keydown', this.handleKeyDown);
    window.addEventListener('keyup', this.handleKeyUp);
  },
  beforeDestroy() {
    window.removeEventListener('keydown', this.handleKeyDown);
    window.removeEventListener('keyup', this.handleKeyUp);
  },
  methods: {
    handleKeyDown(event) {
      this.$store.dispatch('keyboard/handleKeyDown', event.key);
    },
    handleKeyUp(event) {
      this.$store.dispatch('keyboard/handleKeyUp', event.key);
    },
  },
};
</script>

4. 在其他组件中使用键盘事件状态

其他组件可以通过 Vuex 获取当前按下的键或进行其他操作:

// AnotherComponent.vue
<template>
  <div>
    <p>Currently pressed keys: {{ pressedKeys.join(', ') }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters('keyboard', ['getPressedKeys']),
  },
};
</script>

注:混入的另一种处理

假如:系统分模块,然后都注入到 /src/store/index.js中,需要对 keyboard.js 进行修改

/**
 * 全局键盘监听事件
 * 考虑到需要键盘监听事件的组件比较多,所以这里不用混入的的方式
 * 只在 在主应用中监听键盘事件 组测监听
 */
const state = {
	pressedKeys: [], // 记录当前按下的键
};

const getters = {
	getPressedKeys: (state) => state.pressedKeys, // 获取当前按下的所有键
};

const mutations = {
	// 当按下键时,将键添加到 pressedKeys 数组中,并增加按键计数
	SET_KEY_DOWN(state, key) {
		if (!state.pressedKeys.includes(key)) {
			state.pressedKeys.push(key);
		}
	},

	// 当抬起键时,将键从 pressedKeys 数组中移除
	SET_KEY_UP(state, key) {
		const index = state.pressedKeys.indexOf(key);
		if (index !== -1) {
			state.pressedKeys.splice(index, 1);
		}
	},

	// 重置按键状态,清空 pressedKeys 和 keyCount
	RESET_KEYBOARD_STATE(state) {
		state.pressedKeys = [];
	}
};

const actions = {
	handleKeyDown({commit}, key) {
		commit("SET_KEY_DOWN", key);
	},

	handleKeyUp({commit}, key) {
		commit("SET_KEY_UP", key);
	},

	resetKeyboardState({commit}) {
		commit("RESET_KEYBOARD_STATE");
	}
};

export default {
	namespaced: true,
	state,
	getters,
	actions,
	mutations
};


import Vue from "vue";
import Vuex from "vuex";

import user from "./user";
import info from "./info";
import keyboard from "@/store/keyboard";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    user,
    info,
    keyboard // 将 keyboard 模块注入到 Vuex Store 中
  },

});

方法 2:使用 Event Bus全局事件总线

如果不想使用 Vuex,可以通过 Event Bus 来实现全局事件的监听和派发。这种方式在 Vue 2.x 中尤其常见,但需要注意的是,在 Vue 3.x 中推荐使用 VuexPinia 等状态管理工具。

创建 Event Bus

// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();

App.vue 中监听键盘事件

// App.vue
<template>
  <div>
    <h1>Keyboard Event Handler</h1>
    <p>Pressed Keys: {{ pressedKeys.join(', ') }}</p>
  </div>
</template>

<script>
import { EventBus } from './event-bus';

export default {
  data() {
    return {
      pressedKeys: [],
    };
  },
  mounted() {
    window.addEventListener('keydown', this.handleKeyDown);
    window.addEventListener('keyup', this.handleKeyUp);
  },
  beforeDestroy() {
    window.removeEventListener('keydown', this.handleKeyDown);
    window.removeEventListener('keyup', this.handleKeyUp);
  },
  methods: {
    handleKeyDown(event) {
      this.pressedKeys.push(event.key);
      EventBus.$emit('keyDown', event.key);
    },
    handleKeyUp(event) {
      this.pressedKeys = this.pressedKeys.filter(key => key !== event.key);
      EventBus.$emit('keyUp', event.key);
    },
  },
};
</script>

在其他组件中监听事件

// AnotherComponent.vue
<template>
  <div>
    <p>Currently pressed keys: {{ pressedKeys.join(', ') }}</p>
  </div>
</template>

<script>
import { EventBus } from './event-bus';

export default {
  data() {
    return {
      pressedKeys: [],
    };
  },
  mounted() {
    EventBus.$on('keyDown', this.handleKeyDown);
    EventBus.$on('keyUp', this.handleKeyUp);
  },
  beforeDestroy() {
    EventBus.$off('keyDown', this.handleKeyDown);
    EventBus.$off('keyUp', this.handleKeyUp);
  },
  methods: {
    handleKeyDown(key) {
      this.pressedKeys.push(key);
    },
    handleKeyUp(key) {
      this.pressedKeys = this.pressedKeys.filter(k => k !== key);
    },
  },
};
</script>

总结:

  1. 全局管理键盘事件:如同 WebSocket 一样,应该将键盘事件的监听和处理逻辑提升到全局层面,避免每个组件单独管理,这样可以提高性能和可维护性。

  2. Vuex:推荐使用 Vuex 作为全局状态管理工具,通过它来统一管理键盘状态和处理逻辑。

  3. Event Bus:如果项目中不使用 Vuex,也可以通过 Event Bus 来实现类似的全局事件监听和派发,虽然这种方式在 Vue 3 中不推荐使用,但仍然是一种可行的解决方案。

  4. 组件层级的解耦:通过全局事件管理,确保你的组件只关心它们自己的业务逻辑,而不需要处理全局事件绑定和移除问题。

希望这能够帮助你更好地理解如何进行全局的键盘事件管理,并避免将逻辑分散到每个组件中。