键盘事件

学习Canvas键盘事件处理,掌握按键检测、快捷键实现和键盘控制方法。键盘事件用于处理用户的键盘输入,常用于游戏控制、快捷键和表单输入等场景。

键盘事件类型

事件列表

事件说明触发时机
keydown按键按下按键被按下时
keyup按键释放按键被释放时
keypress字符键按下已废弃,不推荐使用

基本监听

document.addEventListener('keydown', (e) => {
  console.log('按键按下:', e.key)
})

document.addEventListener('keyup', (e) => {
  console.log('按键释放:', e.key)
})

事件对象属性

常用属性

document.addEventListener('keydown', (e) => {
  console.log('按键值:', e.key)
  console.log('按键码:', e.code)
  console.log('键码(已废弃):', e.keyCode)
  console.log('是否重复:', e.repeat)
  console.log('Ctrl键:', e.ctrlKey)
  console.log('Shift键:', e.shiftKey)
  console.log('Alt键:', e.altKey)
  console.log('Meta键:', e.metaKey)
})

key与code区别

document.addEventListener('keydown', (e) => {
  // key: 按键的字符值
  // code: 按键的物理位置
  
  // 例如:按下 'a' 键
  // e.key = 'a' (小写) 或 'A' (大写时)
  // e.code = 'KeyA' (始终不变)
  
  // 例如:按下左边数字键 '1'
  // e.key = '1'
  // e.code = 'Digit1'
  
  // 例如:按下右边数字键盘 '1'
  // e.key = '1'
  // e.code = 'Numpad1'
})

键盘事件演示

键盘事件测试(点击画布后按键)

按键状态追踪

按键状态管理

const keyState = {}

document.addEventListener('keydown', (e) => {
  keyState[e.code] = true
})

document.addEventListener('keyup', (e) => {
  keyState[e.code] = false
})

function isKeyDown(code) {
  return keyState[code] === true
}

function gameLoop() {
  if (isKeyDown('ArrowUp')) {
    player.y -= speed
  }
  if (isKeyDown('ArrowDown')) {
    player.y += speed
  }
  if (isKeyDown('ArrowLeft')) {
    player.x -= speed
  }
  if (isKeyDown('ArrowRight')) {
    player.x += speed
  }
  
  requestAnimationFrame(gameLoop)
}

gameLoop()

按键状态类

class KeyboardState {
  constructor() {
    this.keys = new Map()
    this.justPressed = new Set()
    this.justReleased = new Set()
    
    this.bindEvents()
  }
  
  bindEvents() {
    document.addEventListener('keydown', (e) => {
      if (!this.keys.get(e.code)) {
        this.justPressed.add(e.code)
      }
      this.keys.set(e.code, true)
    })
    
    document.addEventListener('keyup', (e) => {
      this.keys.set(e.code, false)
      this.justReleased.add(e.code)
    })
  }
  
  isDown(code) {
    return this.keys.get(code) === true
  }
  
  isUp(code) {
    return !this.isDown(code)
  }
  
  wasPressed(code) {
    return this.justPressed.has(code)
  }
  
  wasReleased(code) {
    return this.justReleased.has(code)
  }
  
  update() {
    this.justPressed.clear()
    this.justReleased.clear()
  }
}

const keyboard = new KeyboardState()

function gameLoop() {
  if (keyboard.wasPressed('Space')) {
    player.jump()
  }
  
  if (keyboard.isDown('ArrowRight')) {
    player.moveRight()
  }
  
  keyboard.update()
  requestAnimationFrame(gameLoop)
}

快捷键实现

组合键检测

document.addEventListener('keydown', (e) => {
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault()
    console.log('保存')
  }
  
  if (e.ctrlKey && e.shiftKey && e.key === 'S') {
    e.preventDefault()
    console.log('另存为')
  }
  
  if (e.ctrlKey && e.key === 'z') {
    e.preventDefault()
    console.log('撤销')
  }
  
  if (e.ctrlKey && e.key === 'y') {
    e.preventDefault()
    console.log('重做')
  }
})

快捷键管理器

class ShortcutManager {
  constructor() {
    this.shortcuts = new Map()
    this.bindEvents()
  }
  
  bindEvents() {
    document.addEventListener('keydown', (e) => {
      const combo = this.getCombo(e)
      const handler = this.shortcuts.get(combo)
      
      if (handler) {
        e.preventDefault()
        handler(e)
      }
    })
  }
  
  getCombo(e) {
    const parts = []
    if (e.ctrlKey) parts.push('Ctrl')
    if (e.shiftKey) parts.push('Shift')
    if (e.altKey) parts.push('Alt')
    if (e.metaKey) parts.push('Meta')
    parts.push(e.key)
    return parts.join('+')
  }
  
  register(combo, handler) {
    this.shortcuts.set(combo, handler)
    return this
  }
  
  unregister(combo) {
    this.shortcuts.delete(combo)
    return this
  }
}

const shortcuts = new ShortcutManager()

shortcuts
  .register('Ctrl+S', () => save())
  .register('Ctrl+Z', () => undo())
  .register('Ctrl+Y', () => redo())
  .register('Delete', () => deleteSelected())

游戏控制

移动控制

const player = {
  x: 200,
  y: 150,
  speed: 5
}

const keys = {}

document.addEventListener('keydown', (e) => {
  keys[e.code] = true
})

document.addEventListener('keyup', (e) => {
  keys[e.code] = false
})

function update() {
  if (keys['KeyW'] || keys['ArrowUp']) player.y -= player.speed
  if (keys['KeyS'] || keys['ArrowDown']) player.y += player.speed
  if (keys['KeyA'] || keys['ArrowLeft']) player.x -= player.speed
  if (keys['KeyD'] || keys['ArrowRight']) player.x += player.speed
}

function gameLoop() {
  update()
  render()
  requestAnimationFrame(gameLoop)
}

游戏控制演示

游戏控制(WASD或方向键移动,空格跳跃)

文本输入

获取文本输入

let inputText = ''
let cursorVisible = true

document.addEventListener('keydown', (e) => {
  if (e.key === 'Backspace') {
    inputText = inputText.slice(0, -1)
  } else if (e.key === 'Enter') {
    console.log('提交:', inputText)
    inputText = ''
  } else if (e.key.length === 1) {
    inputText += e.key
  }
})

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  
  ctx.fillStyle = '#fff'
  ctx.fillRect(50, 100, 300, 30)
  ctx.strokeStyle = '#3498db'
  ctx.strokeRect(50, 100, 300, 30)
  
  ctx.fillStyle = '#333'
  ctx.font = '16px Arial'
  ctx.textAlign = 'left'
  ctx.fillText(inputText, 55, 120)
  
  if (cursorVisible) {
    const textWidth = ctx.measureText(inputText).width
    ctx.fillStyle = '#333'
    ctx.fillRect(55 + textWidth, 105, 1, 20)
  }
  
  requestAnimationFrame(render)
}

setInterval(() => {
  cursorVisible = !cursorVisible
}, 500)

render()

使用隐藏输入框

const hiddenInput = document.createElement('input')
hiddenInput.style.position = 'absolute'
hiddenInput.style.opacity = '0'
hiddenInput.style.pointerEvents = 'none'
document.body.appendChild(hiddenInput)

canvas.addEventListener('click', () => {
  hiddenInput.focus()
})

hiddenInput.addEventListener('input', (e) => {
  console.log('输入:', e.target.value)
})

Canvas焦点

使Canvas可聚焦

canvas.tabIndex = 0

canvas.addEventListener('focus', () => {
  console.log('Canvas获得焦点')
})

canvas.addEventListener('blur', () => {
  console.log('Canvas失去焦点')
})

canvas.addEventListener('click', () => {
  canvas.focus()
})

焦点样式

canvas.addEventListener('focus', () => {
  canvas.style.outline = '2px solid #3498db'
})

canvas.addEventListener('blur', () => {
  canvas.style.outline = 'none'
})

防止默认行为

document.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    e.preventDefault()
  }
  
  if (e.ctrlKey && e.key === 'a') {
    e.preventDefault()
  }
  
  if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
    e.preventDefault()
  }
})