鼠标事件

深入学习Canvas鼠标事件处理,掌握点击、移动、滚轮等鼠标事件的监听和响应方法。鼠标事件是Canvas交互的基础,通过监听鼠标的各种操作实现丰富的交互功能。

鼠标事件类型

事件列表

事件说明触发时机
click点击鼠标按下并释放
dblclick双击快速连续点击两次
mousedown按下鼠标按钮按下
mouseup释放鼠标按钮释放
mousemove移动鼠标在元素上移动
mouseenter进入鼠标进入元素
mouseleave离开鼠标离开元素
mouseover悬停鼠标悬停在元素上
mouseout移出鼠标移出元素
contextmenu右键菜单右键点击
wheel滚轮鼠标滚轮滚动

基本监听

const canvas = document.getElementById('myCanvas')

canvas.addEventListener('click', (e) => {
  console.log('点击事件')
})

canvas.addEventListener('mousedown', (e) => {
  console.log('鼠标按下, 按钮:', e.button)
})

canvas.addEventListener('mouseup', (e) => {
  console.log('鼠标释放')
})

canvas.addEventListener('mousemove', (e) => {
  console.log('鼠标移动')
})

鼠标按钮

button属性

canvas.addEventListener('mousedown', (e) => {
  switch (e.button) {
    case 0:
      console.log('左键按下')
      break
    case 1:
      console.log('中键按下')
      break
    case 2:
      console.log('右键按下')
      break
  }
})

buttons属性

buttons属性表示当前按下的按钮组合:

canvas.addEventListener('mousemove', (e) => {
  if (e.buttons & 1) console.log('左键按下')
  if (e.buttons & 2) console.log('右键按下')
  if (e.buttons & 4) console.log('中键按下')
})

鼠标事件演示

鼠标事件测试

移动鼠标或点击查看事件信息

右键菜单

阻止默认菜单

canvas.addEventListener('contextmenu', (e) => {
  e.preventDefault()
  
  showCustomMenu(e.clientX, e.clientY)
})

function showCustomMenu(x, y) {
  const menu = document.createElement('div')
  menu.className = 'context-menu'
  menu.style.left = x + 'px'
  menu.style.top = y + 'px'
  menu.innerHTML = `
    <div class="menu-item" data-action="copy">复制</div>
    <div class="menu-item" data-action="paste">粘贴</div>
    <div class="menu-item" data-action="delete">删除</div>
  `
  
  document.body.appendChild(menu)
  
  menu.addEventListener('click', (e) => {
    const action = e.target.dataset.action
    console.log('执行操作:', action)
    menu.remove()
  })
  
  document.addEventListener('click', () => {
    menu.remove()
  }, { once: true })
}

鼠标滚轮

滚轮事件

canvas.addEventListener('wheel', (e) => {
  e.preventDefault()
  
  const delta = e.deltaY
  
  if (e.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
    console.log('像素模式, 滚动:', delta)
  } else if (e.deltaMode === WheelEvent.DOM_DELTA_LINE) {
    console.log('行模式, 滚动:', delta)
  } else if (e.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
    console.log('页模式, 滚动:', delta)
  }
})

缩放功能

let scale = 1
const minScale = 0.5
const maxScale = 3

canvas.addEventListener('wheel', (e) => {
  e.preventDefault()
  
  const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1
  const newScale = scale * zoomFactor
  
  if (newScale >= minScale && newScale <= maxScale) {
    const rect = canvas.getBoundingClientRect()
    const mouseX = e.clientX - rect.left
    const mouseY = e.clientY - rect.top
    
    scale = newScale
    
    console.log(`缩放: ${scale.toFixed(2)}x`)
  }
})

鼠标拖拽

基本拖拽

let isDragging = false
let dragStartX = 0
let dragStartY = 0
let offsetX = 0
let offsetY = 0

canvas.addEventListener('mousedown', (e) => {
  isDragging = true
  const rect = canvas.getBoundingClientRect()
  dragStartX = e.clientX - rect.left - offsetX
  dragStartY = e.clientY - rect.top - offsetY
})

canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return
  
  const rect = canvas.getBoundingClientRect()
  offsetX = e.clientX - rect.left - dragStartX
  offsetY = e.clientY - rect.top - dragStartY
  
  redraw()
})

canvas.addEventListener('mouseup', () => {
  isDragging = false
})

canvas.addEventListener('mouseleave', () => {
  isDragging = false
})

鼠标绘制

自由绘制

let isDrawing = false
let lastX = 0
let lastY = 0

canvas.addEventListener('mousedown', (e) => {
  isDrawing = true
  const rect = canvas.getBoundingClientRect()
  lastX = e.clientX - rect.left
  lastY = e.clientY - rect.top
})

canvas.addEventListener('mousemove', (e) => {
  if (!isDrawing) return
  
  const rect = canvas.getBoundingClientRect()
  const x = e.clientX - rect.left
  const y = e.clientY - rect.top
  
  ctx.beginPath()
  ctx.moveTo(lastX, lastY)
  ctx.lineTo(x, y)
  ctx.stroke()
  
  lastX = x
  lastY = y
})

canvas.addEventListener('mouseup', () => {
  isDrawing = false
})

绘制演示

鼠标绘制(按住鼠标拖动绘制)

鼠标光标

动态光标

const cursorStates = {
  default: 'default',
  pointer: 'pointer',
  move: 'move',
  crosshair: 'crosshair',
  text: 'text',
  wait: 'wait',
  notAllowed: 'not-allowed',
  grab: 'grab',
  grabbing: 'grabbing'
}

function setCursor(type) {
  canvas.style.cursor = cursorStates[type] || 'default'
}

canvas.addEventListener('mousemove', (e) => {
  const shape = findShapeAt(e.offsetX, e.offsetY)
  
  if (shape) {
    if (shape.draggable) {
      setCursor('move')
    } else {
      setCursor('pointer')
    }
  } else {
    setCursor('default')
  }
})

自定义光标

function setCustomCursor(imageUrl, x = 0, y = 0) {
  const img = new Image()
  img.src = imageUrl
  img.onload = () => {
    canvas.style.cursor = `url(${imageUrl}) ${x} ${y}, auto`
  }
}

事件优化

节流

function throttle(fn, delay) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= delay) {
      lastTime = now
      fn.apply(this, args)
    }
  }
}

canvas.addEventListener('mousemove', throttle((e) => {
  console.log('节流后的鼠标位置:', e.offsetX, e.offsetY)
}, 16))

防抖

function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

canvas.addEventListener('mousemove', debounce((e) => {
  console.log('防抖后的鼠标位置:', e.offsetX, e.offsetY)
}, 100))