You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue2实现鼠标键盘录播功能及键盘模拟失效问题求助

键盘事件模拟失效的修复方案

核心问题分析

  1. 浏览器安全限制:原生KeyboardEvent仅能触发事件监听,但无法直接修改输入框的文本内容——这是浏览器为防止恶意脚本伪造输入设置的限制。
  2. 事件目标错误:你把事件派发给了document,但实际键盘事件应该派发给当时聚焦的目标元素。
  3. 录制信息缺失:未记录键盘事件发生时的目标元素,回放时无法准确找到要操作的元素。
  4. 点击模拟逻辑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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 21:27:09