理解坐标系统是使用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的画布:
超出范围的绑制会被裁剪,不会报错:
// 这个矩形部分在画布外,只显示可见部分
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倍
坐标变换的内容在后续章节详细讲解。
这是实际开发中常见的需求:
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)