canvas如何监听键盘事件

3,262 阅读1分钟

对于canvas元素,它支持javascript所有鼠标事件,但是如果监听键盘事件则并不会生效。

// 有效
canvas.addEventListener('click', (e) => {
  console.log('触发点击了')
})

// 无效
canvas.addEventListener('keydown', (e) => {
  console.log('触发按键了')
})

其原因在于,键盘输入事件只发生在当前拥有焦点的HTML元素上,如果没有元素拥有焦点,那么事件将会上移至windows和document对象,所以有两种常用方法来解决这个问题:

  1. 如果canvas元素和windows长宽基本一致,可以通过在windows对象上绑定键盘事件来代替对canvas元素的监听与处理。

    window.addEventListener('keydown', doKeyDown, true)
    
  2. 让canvas元素处于聚焦状态,并给它绑定键盘事件

    <canvas tabindex="0"></canvas>
    

    tabindex设置为0或更大。

下面通过示例详细的介绍第二种方法:

<!-- html部分 -->
<canvas id="canvas" tabindex="0"></canvas>
// js部分
const canvas = document.getElementById('canvas')
canvas.focus()
canvas.addEventListener('keydown', (e) => {
  console.log(`keyCode: ${e.keyCode}`)
})

这样就可以让canvas在一开始处于聚焦状态,并相应键盘输入事件。

不过tabindex聚焦的元素会有一层默认的外框,标识该元素处于聚焦状态。如果不想要显示外框,可以通过css样式去除:

canvas:focus {
  outline:none;
}

可以写一个实际应用来测试,比如用键盘的上下左右或者wsad键操作一个小方块,在canvas画布中移动。

image-20210527180217366

<!-- html部分 -->
<canvas id="canvas" tabindex="0"></canvas>

<!-- css部分 -->
<style>
  #canvas{
    width: 100vw;
    height: 100vh;
    background-color: #4ab7bd;
  }
  #canvas:focus{
    outline: none;
  }
</style>

<!-- js部分 -->
<script>
window.onload = function() {
  // 画布的长宽
  const canvas = document.getElementById('canvas')
  const canvasWidth = canvas.clientWidth
  const canvasHeight = canvas.clientHeight
  // 在画布上移动的方块的长宽
  const [rectWidth, rectHeight] = [40, 40]
  // 方块的横纵坐标
  let [rectX, rectY] = [0, 0]
  // 初始化
  canvas.width = canvasWidth
  canvas.height = canvasHeight
  let context = canvas.getContext('2d')
  // 给方块设置颜色和初始坐标(中心点),绘制
  context.fillStyle = 'red'
  rectX = (canvasWidth - rectWidth) / 2
  rectY = (canvasHeight - rectHeight) / 2
  context.fillRect(rectX, rectY, rectWidth, rectHeight)

  // canvas元素上监听键盘输入事件
  canvas.addEventListener('keydown', doKeyDown, true)
  canvas.focus() // 让canvas聚焦

  function clearCanvas() {
    context.clearRect(0, 0, canvasWidth, canvasHeight)
  }

  function doKeyDown(e) {
    // 获取keyCode
    const keyCode = e.keyCode ? e.keyCode : e.which

    // 向上箭头 / w,让纵坐标向上移动10
    if (keyCode === 38 || keyCode === 87) {
      clearCanvas()
      rectY -= 10
      if (rectY < 0) {
        rectY = 0
      }
      context.fillRect(rectX, rectY, rectWidth, rectHeight)
    }

    // 向下箭头 / s,让纵坐标向下移动10
    if (keyCode === 40 || keyCode === 83) {
      clearCanvas()
      rectY += 10
      if (rectY > canvasHeight - rectHeight) {
        rectY = canvasHeight - rectHeight
      }
      context.fillRect(rectX, rectY, rectWidth, rectHeight)
    }

    // 向左箭头 / a,让纵坐标向左移动10
    if (keyCode === 37 || keyCode === 65) {
      clearCanvas()
      rectX -= 10
      if (rectX < 0) {
        rectX = 0
      }
      context.fillRect(rectX, rectY, rectWidth, rectHeight)
    }

    // 向右箭头 / d,让纵坐标向右移动10
    if (keyCode === 39 || keyCode === 68) {
      clearCanvas()
      rectX += 10
      if (rectX > canvasWidth - rectWidth) {
        rectX = canvasWidth - rectWidth
      }
      context.fillRect(rectX, rectY, rectWidth, rectHeight)
    }
  }		  
}
</script>

当canvas元素处于聚焦状态时,可以监听到键盘事件,当其失去焦点时,则也会失去键盘监听。

我们可以基于此进行canvas小游戏开发,比如贪吃蛇、推箱子、走迷宫、射击、俄罗斯方块等等。