Canvas 坐标系统

理解坐标系统是使用Canvas绑制图形的基础。Canvas使用的是二维笛卡尔坐标系,但与数学中的坐标系有些不同。

坐标系基础

Canvas坐标系的原点(0, 0)在画布的左上角

(0,0) ────────→ X轴
  │
  │
  │
  ↓
  Y轴

这与数学中的坐标系不同(数学坐标系原点在左下角,Y轴向上)。

坐标值含义

参数含义
x水平位置,向右递增
y垂直位置,向下递增
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

// 在坐标(100, 50)位置绑制一个点
ctx.fillRect(100, 50, 1, 1)

// 在坐标(200, 100)位置绑制一个10x10的矩形
ctx.fillRect(200, 100, 10, 10)

坐标与画布尺寸的关系

<canvas id="canvas" width="400" height="300"></canvas>

对于这个400x300的画布:

  • x的有效范围:0 ~ 399
  • y的有效范围:0 ~ 299
  • 右下角坐标:(399, 299)

超出范围的绑制会被裁剪,不会报错:

// 这个矩形部分在画布外,只显示可见部分
ctx.fillRect(350, 250, 100, 100)

实际示例:绘制坐标网格

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

const width = canvas.width
const height = canvas.height

// 绘制网格线
ctx.strokeStyle = '#eee'
ctx.lineWidth = 1

// 垂直线
for (let x = 0; x <= width; x += 50) {
  ctx.beginPath()
  ctx.moveTo(x, 0)
  ctx.lineTo(x, height)
  ctx.stroke()
}

// 水平线
for (let y = 0; y <= height; y += 50) {
  ctx.beginPath()
  ctx.moveTo(0, y)
  ctx.lineTo(width, y)
  ctx.stroke()
}

// 标注坐标
ctx.fillStyle = '#999'
ctx.font = '12px Arial'
ctx.fillText('(0,0)', 5, 15)
ctx.fillText(`(${width},${height})`, width - 50, height - 5)

坐标变换的影响

Canvas支持坐标变换,变换会影响后续所有绑制:

// 平移坐标原点
ctx.translate(100, 100)

// 现在原点在原来的(100, 100)位置
// 这里的(0, 0)实际显示在画布的(100, 100)
ctx.fillRect(0, 0, 50, 50)

// 旋转(弧度)
ctx.rotate(Math.PI / 4)  // 旋转45度

// 缩放
ctx.scale(2, 2)  // 放大2倍

坐标变换的内容在后续章节详细讲解。

获取鼠标在Canvas中的坐标

这是实际开发中常见的需求:

const canvas = document.getElementById('canvas')

canvas.addEventListener('click', function(e) {
  const rect = canvas.getBoundingClientRect()
  
  const x = e.clientX - rect.left
  const y = e.clientY - rect.top
  
  console.log(`点击坐标:(${x}, ${y})`)
})

封装成函数

function getCanvasCoordinates(canvas, event) {
  const rect = canvas.getBoundingClientRect()
  
  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top
  }
}

// 使用
canvas.addEventListener('mousemove', function(e) {
  const pos = getCanvasCoordinates(canvas, e)
  console.log(`鼠标位置:${pos.x}, ${pos.y}`)
})

处理高清屏的坐标问题

高清屏(devicePixelRatio > 1)上,物理像素和CSS像素不一致:

function getCanvasCoordinates(canvas, event) {
  const rect = canvas.getBoundingClientRect()
  const dpr = window.devicePixelRatio || 1
  
  return {
    x: (event.clientX - rect.left) * dpr,
    y: (event.clientY - rect.top) * dpr
  }
}

坐标系练习:绘制坐标系

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const w = canvas.width
const h = canvas.height

// 绘制X轴
ctx.beginPath()
ctx.moveTo(0, h / 2)
ctx.lineTo(w, h / 2)
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.stroke()

// 绘制Y轴
ctx.beginPath()
ctx.moveTo(w / 2, 0)
ctx.lineTo(w / 2, h)
ctx.stroke()

// 绘制刻度
ctx.fillStyle = '#666'
ctx.font = '10px Arial'

for (let i = -5; i <= 5; i++) {
  if (i === 0) continue
  
  // X轴刻度
  const x = w / 2 + i * 50
  ctx.fillRect(x - 1, h / 2 - 5, 2, 10)
  ctx.fillText(i * 50, x - 10, h / 2 + 20)
  
  // Y轴刻度
  const y = h / 2 + i * 50
  ctx.fillRect(w / 2 - 5, y - 1, 10, 2)
  ctx.fillText(-i * 50, w / 2 + 10, y + 4)
}

// 原点标记
ctx.fillText('O', w / 2 - 15, h / 2 + 15)