事件对象

在事件处理中,我们经常需要访问原生 DOM 事件对象:获取鼠标位置、读取键盘按键、阻止默认行为...Vue 提供了多种方式来访问这个事件对象。

什么是事件对象

当 DOM 事件触发时,浏览器会创建一个事件对象,包含事件的详细信息:

{
  type: 'click',           // 事件类型
  target: element,         // 触发事件的元素
  currentTarget: element,  // 绑定事件的元素
  clientX: 100,            // 鼠标 X 坐标
  clientY: 200,            // 鼠标 Y 坐标
  key: 'Enter',            // 按键值
  keyCode: 13,             // 按键码
  preventDefault: fn,      // 阻止默认行为
  stopPropagation: fn      // 阻止冒泡
}

访问事件对象的方式

方式一:自动传入(无参数时)

当事件处理器没有参数时,Vue 会自动将事件对象作为第一个参数传入:

<button @click="handleClick">点击</button>

<script>
methods: {
  handleClick: function(event) {
    console.log(event.type)      // 'click'
    console.log(event.target)    // <button> 元素
    console.log(event.clientX)   // 鼠标 X 坐标
  }
}
</script>

这是最简单的方式,适用于不需要传递额外参数的场景。

方式二:使用 $event 变量

当需要同时传递自定义参数和事件对象时,使用 $event 变量:

<button @click="handle('按钮', $event)">点击</button>

<script>
methods: {
  handle: function(message, event) {
    console.log(message)       // '按钮'
    console.log(event.type)    // 'click'
    console.log(event.target)  // <button> 元素
  }
}
</script>

$event 是 Vue 提供的特殊变量,代表原生 DOM 事件对象。

方式三:箭头函数包装

也可以使用箭头函数来传递参数:

<button @click="(e) => handleClick(item, e)">点击</button>

<script>
methods: {
  handleClick: function(item, event) {
    console.log(item.name)
    console.log(event.target)
  }
}
</script>

这种方式更灵活,但代码稍长。

事件对象的常用属性

鼠标事件

<div 
  @click="handleClick"
  @mousemove="handleMouseMove"
  @mousedown="handleMouseDown"
  @mouseup="handleMouseUp"
  @dblclick="handleDoubleClick"
  @contextmenu="handleRightClick"
>
  鼠标操作区域
</div>

<script>
methods: {
  handleClick: function(event) {
    // 鼠标点击位置
    console.log('X:', event.clientX)
    console.log('Y:', event.clientY)
    
    // 相对于触发元素的坐标
    console.log('offsetX:', event.offsetX)
    console.log('offsetY:', event.offsetY)
    
    // 相对于页面的坐标
    console.log('pageX:', event.pageX)
    console.log('pageY:', event.pageY)
    
    // 相对于屏幕的坐标
    console.log('screenX:', event.screenX)
    console.log('screenY:', event.screenY)
    
    // 按键信息
    console.log('左键:', event.button === 0)
    console.log('中键:', event.button === 1)
    console.log('右键:', event.button === 2)
    
    // 修饰键状态
    console.log('Ctrl:', event.ctrlKey)
    console.log('Shift:', event.shiftKey)
    console.log('Alt:', event.altKey)
    console.log('Meta:', event.metaKey)
  },
  
  handleMouseMove: function(event) {
    // 实时获取鼠标位置
    this.mouseX = event.clientX
    this.mouseY = event.clientY
  },
  
  handleRightClick: function(event) {
    event.preventDefault()  // 阻止默认右键菜单
    // 显示自定义菜单
    this.showContextMenu(event.clientX, event.clientY)
  }
}
</script>

键盘事件

<input 
  @keydown="handleKeyDown"
  @keyup="handleKeyUp"
  placeholder="按下键盘"
>

<script>
methods: {
  handleKeyDown: function(event) {
    // 按键值(推荐)
    console.log('key:', event.key)       // 'Enter', 'a', '1', 'ArrowUp'
    console.log('code:', event.code)     // 'Enter', 'KeyA', 'Digit1', 'ArrowUp'
    
    // 按键码(已废弃,但仍可用)
    console.log('keyCode:', event.keyCode)
    
    // 修饰键状态
    console.log('Ctrl:', event.ctrlKey)
    console.log('Shift:', event.shiftKey)
    console.log('Alt:', event.altKey)
    
    // 阻止默认行为
    if (event.key === 'Enter') {
      event.preventDefault()
      this.submit()
    }
  }
}
</script>

表单事件

<form @submit="handleSubmit">
  <input type="text" @input="handleInput" @change="handleChange">
  <input type="checkbox" @change="handleCheck">
  <select @change="handleSelect">
    <option value="1">选项一</option>
    <option value="2">选项二</option>
  </select>
</form>

<script>
methods: {
  handleSubmit: function(event) {
    event.preventDefault()  // 阻止表单默认提交
    console.log('表单提交')
  },
  
  handleInput: function(event) {
    // 实时获取输入值
    console.log(event.target.value)
  },
  
  handleChange: function(event) {
    // 值改变时触发
    console.log('新值:', event.target.value)
    console.log('checked:', event.target.checked)  // 复选框
  },
  
  handleSelect: function(event) {
    // 下拉选择
    console.log('选中:', event.target.value)
  }
}
</script>

target vs currentTarget

这两个属性经常混淆,区别在于:

  • event.target:触发事件的元素(可能是子元素)
  • event.currentTarget:绑定事件的元素(始终是当前元素)
<div class="outer" @click="handleClick">
  <button class="inner">按钮</button>
</div>

<script>
methods: {
  handleClick: function(event) {
    console.log('target:', event.target)         // <button> 元素
    console.log('currentTarget:', event.currentTarget)  // <div> 元素
    
    // 判断是否点击的是绑定事件的元素本身
    if (event.target === event.currentTarget) {
      console.log('点击的是外层 div')
    }
  }
}
</script>

实战示例

拖拽功能

<div 
  class="draggable"
  :style="{ left: x + 'px', top: y + 'px' }"
  @mousedown="startDrag"
>
  拖拽我
</div>

<script>
new Vue({
  data: {
    isDragging: false,
    x: 100,
    y: 100,
    startX: 0,
    startY: 0
  },
  methods: {
    startDrag: function(event) {
      this.isDragging = true
      this.startX = event.clientX - this.x
      this.startY = event.clientY - this.y
      
      document.addEventListener('mousemove', this.drag)
      document.addEventListener('mouseup', this.stopDrag)
    },
    
    drag: function(event) {
      if (!this.isDragging) return
      
      this.x = event.clientX - this.startX
      this.y = event.clientY - this.startY
    },
    
    stopDrag: function() {
      this.isDragging = false
      document.removeEventListener('mousemove', this.drag)
      document.removeEventListener('mouseup', this.stopDrag)
    }
  }
})
</script>

右键菜单

<div @contextmenu.prevent="showMenu">
  右键点击显示菜单
</div>

<div 
  v-if="menuVisible" 
  class="context-menu"
  :style="{ left: menuX + 'px', top: menuY + 'px' }"
>
  <div @click="copy">复制</div>
  <div @click="paste">粘贴</div>
  <div @click="delete">删除</div>
</div>

<script>
new Vue({
  data: {
    menuVisible: false,
    menuX: 0,
    menuY: 0
  },
  methods: {
    showMenu: function(event) {
      this.menuX = event.clientX
      this.menuY = event.clientY
      this.menuVisible = true
    },
    
    copy: function() {
      console.log('复制')
      this.menuVisible = false
    },
    
    paste: function() {
      console.log('粘贴')
      this.menuVisible = false
    },
    
    delete: function() {
      console.log('删除')
      this.menuVisible = false
    }
  },
  
  mounted: function() {
    // 点击其他地方关闭菜单
    document.addEventListener('click', this.hideMenu)
  },
  
  beforeDestroy: function() {
    document.removeEventListener('click', this.hideMenu)
  },
  
  methods: {
    hideMenu: function() {
      this.menuVisible = false
    }
  }
})
</script>

鼠标跟随效果

<div class="container" @mousemove="followMouse">
  <div class="follower" :style="followerStyle"></div>
</div>

<script>
new Vue({
  data: {
    mouseX: 0,
    mouseY: 0
  },
  computed: {
    followerStyle: function() {
      return {
        transform: `translate(${this.mouseX}px, ${this.mouseY}px)`
      }
    }
  },
  methods: {
    followMouse: function(event) {
      // 相对于容器的坐标
      const rect = event.currentTarget.getBoundingClientRect()
      this.mouseX = event.clientX - rect.left
      this.mouseY = event.clientY - rect.top
    }
  }
})
</script>

获取触发元素信息

<ul>
  <li 
    v-for="item in items" 
    :key="item.id"
    @click="selectItem(item, $event)"
  >
    {{ item.name }}
  </li>
</ul>

<script>
methods: {
  selectItem: function(item, event) {
    // 获取触发元素
    const element = event.target
    
    // 获取自定义属性
    const id = element.dataset.id
    
    // 获取元素尺寸
    console.log('宽度:', element.offsetWidth)
    console.log('高度:', element.offsetHeight)
    
    // 获取元素位置
    const rect = element.getBoundingClientRect()
    console.log('位置:', rect.left, rect.top)
    
    // 添加样式
    element.classList.add('selected')
  }
}
</script>

事件修饰符 vs 事件对象方法

Vue 提供的事件修饰符和事件对象方法可以互换使用:

<!-- 使用修饰符 -->
<form @submit.prevent="handleSubmit">
<button @click.stop="handleClick">

<!-- 使用事件对象方法 -->
<form @submit="handleSubmit">
<button @click="handleClick">

<script>
methods: {
  handleSubmit: function(event) {
    event.preventDefault()
  },
  handleClick: function(event) {
    event.stopPropagation()
  }
}
</script>

推荐使用修饰符

修饰符让意图更清晰,代码更简洁。但在某些复杂场景下,可能需要在方法中手动调用事件对象方法。

小结

事件对象是处理复杂交互的关键:

  1. 自动传入:无参数时,事件对象自动作为第一个参数
  2. **event变量:有参数时,使用event 变量**:有参数时,使用 `event` 显式传递
  3. 常用属性targetclientX/YkeykeyCode
  4. target vs currentTarget:区分触发元素和绑定元素

掌握事件对象的使用,能让你处理各种复杂的交互场景,从简单的点击到复杂的拖拽、绘图等功能。