Vue2实现鼠标键盘录播功能及键盘模拟失效问题求助
键盘事件模拟失效的修复方案
核心问题分析
- 浏览器安全限制:原生
KeyboardEvent仅能触发事件监听,但无法直接修改输入框的文本内容——这是浏览器为防止恶意脚本伪造输入设置的限制。 - 事件目标错误:你把事件派发给了
document,但实际键盘事件应该派发给当时聚焦的目标元素。 - 录制信息缺失:未记录键盘事件发生时的目标元素,回放时无法准确找到要操作的元素。
- 点击模拟逻辑bug:
performFakeClick中this.targetElement未赋值,导致点击事件无法正确触发。
具体修复步骤
1. 完善录制逻辑,记录目标元素信息
修改handleKeyDown,记录事件目标元素的唯一选择器:
handleKeyDown(event) { // 优先取元素id,没有则用标签名 const targetSelector = event.target.id ? `#${event.target.id}` : event.target.tagName.toLowerCase(); this.events.push({ type: 'keyboard', key: event.key, target: targetSelector }); this.displayEvents(); }
2. 修复点击模拟的bug
修改performFakeClick,正确赋值目标元素并触发点击:
performFakeClick(event) { const targetElement = document.elementFromPoint(event.x, event.y); if (!targetElement) return; if (targetElement.tagName.toLowerCase() === 'input') { targetElement.focus(); } const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); targetElement.dispatchEvent(clickEvent); }
3. 修改键盘事件回放逻辑,直接更新输入框内容+模拟事件
因为纯模拟键盘事件无法修改输入框值,所以需要直接操作value属性,同时触发事件保证监听逻辑生效:
replayNextEvent() { const event = this.events[this.currentIndex]; if (!event) return; if (event.type === 'mouse') { // 原有鼠标事件逻辑保留 if (event.action === 'move') { this.$refs.fakeMouse.style.left = event.x + 'px'; this.$refs.fakeMouse.style.top = event.y + 'px'; } else if (event.action === 'click') { this.performFakeClick(event); } } else if (event.type === 'keyboard') { // 找到目标元素,找不到则用当前聚焦元素 let targetElement = document.querySelector(event.target); if (!targetElement) { targetElement = document.activeElement; } if (targetElement && ['INPUT', 'TEXTAREA'].includes(targetElement.tagName.toUpperCase())) { targetElement.focus(); // 处理不同按键逻辑 if (event.key === 'Backspace') { targetElement.value = targetElement.value.slice(0, -1); } else if (event.key.length === 1) { targetElement.value += event.key; } else if (event.key === 'Enter') { // 单独处理回车事件 targetElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', keyCode: 13, bubbles: true, cancelable: true, view: window })); } // 触发keydown和input事件,保证Vue的v-model或其他监听生效 targetElement.dispatchEvent(new KeyboardEvent('keydown', { key: event.key, bubbles: true, cancelable: true, view: window })); targetElement.dispatchEvent(new Event('input', { bubbles: true })); } } this.events.splice(this.currentIndex, 1); setTimeout(this.replayNextEvent, 100); // 延长间隔,更贴近真实操作节奏 }
修改后的完整Vue代码
<template> <div> <div class="mouse-keyboard-tracker"> <button class="doclick" @click="dome">click</button> <div style="width: 100%; height: 25vh; overflow: scroll;"> <div>{{ events }}</div> </div> </div> <div class="fakeMouse" ref="fakeMouse" style="position: absolute; width: 20px; height: 20px; background-color: rgb(214, 58, 58); border-radius: 100%;"></div> <button @click="startReplay">Replay Events</button> <input id="fakeInput" style="height: 40px;" type="text"> <input id="Input" style="height: 40px;" type="text"> <p>{{ visTxt }}</p> </div> </template> <script> export default { data() { return { events: [], visTxt: [], currentIndex: 0, isReplaying: false }; }, mounted () { document.addEventListener('mousemove', this.handleMouseEvent); document.addEventListener('click', this.handleMouseEvent); document.addEventListener('keydown', this.handleKeyDown); }, methods: { dome() { this.visTxt.push("demo") }, handleMouseEvent(event) { if (event.type === 'mousemove') { this.events.push({ type: 'mouse', action: 'move', x: event.clientX, y: event.clientY }); } else if (event.type === 'click') { this.events.push({ type: 'mouse', action: 'click', x: event.clientX, y: event.clientY }); } this.displayEvents(); }, handleKeyDown(event) { const targetSelector = event.target.id ? `#${event.target.id}` : event.target.tagName.toLowerCase(); this.events.push({ type: 'keyboard', key: event.key, target: targetSelector }); this.displayEvents(); }, displayEvents() { // No need to log to console, events are displayed on the page }, startReplay () { this.currentIndex = 0; this.replayNextEvent(); document.removeEventListener('mousemove', this.handleMouseEvent); document.removeEventListener('click', this.handleMouseEvent); document.removeEventListener('keydown', this.handleKeyDown); }, replayNextEvent () { const event = this.events[this.currentIndex]; if (!event) return; if (event.type === 'mouse') { if (event.action === 'move') { this.$refs.fakeMouse.style.left = event.x + 'px'; this.$refs.fakeMouse.style.top = event.y + 'px'; } else if (event.action === 'click') { this.performFakeClick(event); } } else if (event.type === 'keyboard') { let targetElement = document.querySelector(event.target); if (!targetElement) { targetElement = document.activeElement; } if (targetElement && ['INPUT', 'TEXTAREA'].includes(targetElement.tagName.toUpperCase())) { targetElement.focus(); if (event.key === 'Backspace') { targetElement.value = targetElement.value.slice(0, -1); } else if (event.key.length === 1) { targetElement.value += event.key; } else if (event.key === 'Enter') { targetElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', keyCode: 13, bubbles: true, cancelable: true, view: window })); } targetElement.dispatchEvent(new KeyboardEvent('keydown', { key: event.key, bubbles: true, cancelable: true, view: window })); targetElement.dispatchEvent(new Event('input', { bubbles: true })); } } this.events.splice(this.currentIndex, 1); setTimeout(this.replayNextEvent, 100); }, performFakeClick (event) { const targetElement = document.elementFromPoint(event.x, event.y); if (!targetElement) return; if (targetElement.tagName.toLowerCase() === 'input') { targetElement.focus(); } const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); targetElement.dispatchEvent(clickEvent); } }, beforeDestroy() { document.removeEventListener('mousemove', this.handleMouseEvent); document.removeEventListener('click', this.handleMouseEvent); document.removeEventListener('keydown', this.handleKeyDown); } }; </script> <style scoped> .mouse-keyboard-tracker { padding: 20px; background-color: #f9f9f9; border: 1px solid #ccc; } </style>
内容的提问来源于stack exchange,提问作者Ling Yun
相关产品推荐
相关产品推荐

