Canvas 基础绘图
本文档介绍 Canvas 的基础绘图概念和方法,包括坐标系统、基本形状绘制、路径系统等内容。
Canvas 坐标系统
Canvas 的坐标系是其绘图系统的基础,理解坐标系对于正确绘制图形至关重要。
默认坐标系
code
0 ──────── X 轴(向右为正)───────→
│
│
│ Canvas 绘图区域
│
Y 轴
(向下为正)
↓特点:
- 原点位置:(0, 0) 位于画布的左上角
- X 轴方向:向右为正方向
- Y 轴方向:向下为正方向
- 单位:像素(pixel)
画布尺寸
html
<canvas id="myCanvas" width="500" height="300"></canvas>属性说明:
width:画布的实际宽度(像素),默认 300height:画布的实际高度(像素),默认 150- 坐标范围:x ∈ [0, width],y ∈ [0, height]
注意
Canvas 尺寸应该通过 HTML 属性或 JavaScript 设置,而不是 CSS。CSS 设置的尺寸只影响显示大小,不影响内部分辨率,会导致图形模糊。
坐标变换方法
| 方法 | 说明 | 示例 |
|---|---|---|
translate(x, y) | 平移坐标系原点 | ctx.translate(100, 50) |
scale(sx, sy) | 缩放坐标系 | ctx.scale(2, 0.5) |
rotate(angle) | 旋转坐标系(弧度) | ctx.rotate(Math.PI/4) |
transform(a, b, c, d, e, f) | 应用变换矩阵 | ctx.transform(1, 0.5, -0.5, 1, 0, 0) |
setTransform(a, b, c, d, e, f) | 设置变换矩阵 | ctx.setTransform(1, 0, 0, 1, 0, 0) |
resetTransform() | 重置为默认坐标系 | ctx.resetTransform() |
变换矩阵参数说明:
code
| a c e | a: 水平缩放 c: 垂直倾斜 e: 水平平移
| b d f | b: 水平倾斜 d: 垂直缩放 f: 垂直平移
| 0 0 1 |实际应用示例
自定义坐标系
javascript
// 设置数学坐标系(原点在中心,Y轴向上)
function setupMathCoordinateSystem() {
ctx.save()
const centerX = canvas.width / 2
const centerY = canvas.height / 2
ctx.translate(centerX, centerY)
ctx.scale(1, -1) // 翻转Y轴,使向上为正方向
}
// 使用自定义坐标系绘制
setupMathCoordinateSystem()
ctx.beginPath()
ctx.arc(0, 0, 50, 0, Math.PI * 2) // 在中心绘制圆
ctx.stroke()
ctx.restore() // 恢复默认坐标系视口变换
javascript
// 创建视口(只显示画布的一部分)
function setupViewport(x, y, width, height) {
ctx.save()
ctx.beginPath()
ctx.rect(x, y, width, height)
ctx.clip() // 设置裁剪区域
ctx.translate(-x, -y) // 移动原点到视口左上角
}
setupViewport(100, 100, 200, 150)
ctx.fillRect(0, 0, 400, 300) // 实际只显示部分内容
ctx.restore()像素对齐
javascript
// 确保坐标与像素网格对齐(避免模糊)
function drawPixelPerfect(x, y) {
ctx.save()
ctx.translate(Math.round(x), Math.round(y))
ctx.fillRect(0, 0, 10, 10)
ctx.restore()
}变换顺序
变换是累积的,顺序不同结果不同。建议遵循:平移 → 旋转 → 缩放 的顺序。
矩形绘制
矩形是 Canvas 中唯一可以直接绘制的基本形状,提供了三个专用方法。
方法概览
| 方法 | 说明 | 返回值 |
|---|---|---|
fillRect(x, y, width, height) | 绘制填充矩形 | 无 |
strokeRect(x, y, width, height) | 绘制描边矩形 | 无 |
clearRect(x, y, width, height) | 清除矩形区域 | 无 |
参数说明
x:矩形左上角的 X 坐标y:矩形左上角的 Y 坐标width:矩形的宽度(像素)height:矩形的高度(像素)
注意事项
width和height必须为正值- 如果
width或height为 0,则不会绘制任何内容 - 如果
width或height为负值,某些浏览器可能抛出错误
基本用法
javascript
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 绘制填充矩形
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 100, 50)
// 绘制描边矩形
ctx.strokeStyle = 'blue'
ctx.lineWidth = 3
ctx.strokeRect(10, 70, 100, 50)
// 清除矩形区域
ctx.clearRect(30, 20, 60, 30)完整示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>矩形绘制示例</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 绘制红色填充矩形
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 120, 80)
// 绘制蓝色描边矩形
ctx.strokeStyle = 'blue'
ctx.lineWidth = 3
ctx.strokeRect(200, 50, 120, 80)
// 清除部分区域
ctx.clearRect(80, 70, 60, 40)
// 绘制半透明矩形
ctx.fillStyle = 'rgba(0, 255, 0, 0.5)'
ctx.fillRect(125, 90, 120, 80)
</script>
</body>
</html>rect() 方法
rect(x, y, width, height) 方法创建矩形路径,需要配合 fill() 或 stroke() 使用。
与 fillRect/strokeRect 的区别:
| 特性 | rect() | fillRect()/strokeRect() |
|---|---|---|
| 返回值 | 无 | 无 |
| 是否创建路径 | 是 | 否 |
| 是否直接绘制 | 否,需要 fill()/stroke() | 是 |
| 适用场景 | 复杂路径组合 | 简单矩形绘制 |
javascript
// 使用 rect() 创建矩形路径
ctx.beginPath()
ctx.rect(50, 50, 200, 100)
// 填充矩形
ctx.fillStyle = 'blue'
ctx.fill()
// 描边矩形
ctx.strokeStyle = 'red'
ctx.lineWidth = 2
ctx.stroke()路径系统
路径是 Canvas 绘图的核心概念,所有复杂图形都通过路径系统绘制。
路径方法概览
| 方法 | 描述 |
|---|---|
beginPath() | 开始新路径 |
closePath() | 闭合路径 |
moveTo(x, y) | 移动画笔到指定点 |
lineTo(x, y) | 绘制直线到指定点 |
rect(x, y, width, height) | 创建矩形路径 |
arc(x, y, radius, startAngle, endAngle, anticlockwise) | 创建圆弧路径 |
fill() | 填充路径 |
stroke() | 描边路径 |
clip() | 创建裁剪路径 |
路径绘制流程
code
beginPath() → moveTo()/lineTo()/arc()... → closePath() → fill()/stroke()标准流程:
javascript
// 1. 开始新路径
ctx.beginPath()
// 2. 移动到起始点
ctx.moveTo(50, 50)
// 3. 绘制路径
ctx.lineTo(200, 50)
ctx.lineTo(200, 150)
ctx.lineTo(50, 150)
// 4. 闭合路径(可选)
ctx.closePath()
// 5. 填充或描边
ctx.fillStyle = 'blue'
ctx.fill()
ctx.strokeStyle = 'red'
ctx.lineWidth = 2
ctx.stroke()beginPath() 的重要性
每次开始新的路径绘制时,必须调用 beginPath(),否则新路径会与之前的路径连接。
javascript
// ❌ 错误示例:没有使用 beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.strokeStyle = 'blue'
ctx.stroke()
// 这条线会与上一条线连接
ctx.moveTo(50, 100)
ctx.lineTo(200, 150)
ctx.strokeStyle = 'green'
ctx.stroke()
// ✅ 正确示例:使用 beginPath()
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.strokeStyle = 'blue'
ctx.stroke()
ctx.beginPath() // 开始新路径
ctx.moveTo(50, 100)
ctx.lineTo(200, 150)
ctx.strokeStyle = 'green'
ctx.stroke()线条绘制
moveTo 和 lineTo
moveTo(x, y):移动画笔到指定坐标,不绘制线条lineTo(x, y):从当前点绘制直线到指定坐标
绘制直线
javascript
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 绘制一条直线
ctx.beginPath()
ctx.moveTo(50, 50) // 起点
ctx.lineTo(350, 250) // 终点
ctx.strokeStyle = 'blue'
ctx.lineWidth = 3
ctx.stroke()绘制折线
javascript
// 绘制折线
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.lineTo(200, 150)
ctx.lineTo(350, 150)
ctx.strokeStyle = 'green'
ctx.lineWidth = 2
ctx.stroke()绘制三角形
javascript
// 绘制填充三角形
ctx.beginPath()
ctx.moveTo(200, 50)
ctx.lineTo(100, 150)
ctx.lineTo(300, 150)
ctx.closePath() // 自动闭合路径
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'
ctx.fill()
ctx.strokeStyle = 'black'
ctx.lineWidth = 2
ctx.stroke()绘制多边形
javascript
// 绘制五边形
function drawPolygon(ctx, centerX, centerY, radius, sides) {
ctx.beginPath()
for (let i = 0; i < sides; i++) {
const angle = (i * 2 * Math.PI / sides) - Math.PI / 2
const x = centerX + radius * Math.cos(angle)
const y = centerY + radius * Math.sin(angle)
if (i === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
}
ctx.closePath()
ctx.fillStyle = 'gold'
ctx.fill()
ctx.strokeStyle = 'orange'
ctx.lineWidth = 2
ctx.stroke()
}
drawPolygon(ctx, 200, 150, 80, 5)线条样式
Canvas 提供了多种线条样式属性来控制线条的外观。
基本属性
| 属性 | 说明 | 默认值 | 可选值 |
|---|---|---|---|
lineWidth | 线条宽度 | 1 | 正数(像素) |
lineCap | 线条端点样式 | 'butt' | 'butt', 'round', 'square' |
lineJoin | 线条连接样式 | 'miter' | 'miter', 'round', 'bevel' |
miterLimit | 斜接限制 | 10 | 正数 |
lineDashOffset | 虚线偏移量 | 0 | 数字 |
lineCap 线条端点样式
code
butt: ─────── (平直端点,默认)
round: ╭─────╮ (圆形端点)
square: █─────█ (方形端点,延伸半个线宽)javascript
const lineWidths = ['butt', 'round', 'square']
lineWidths.forEach((cap, index) => {
ctx.beginPath()
ctx.lineCap = cap
ctx.lineWidth = 20
ctx.moveTo(50, 50 + index * 60)
ctx.lineTo(350, 50 + index * 60)
ctx.stroke()
// 绘制参考线
ctx.beginPath()
ctx.strokeStyle = 'red'
ctx.lineWidth = 1
ctx.moveTo(50, 50 + index * 60)
ctx.lineTo(350, 50 + index * 60)
ctx.stroke()
ctx.strokeStyle = 'black'
})lineJoin 线条连接样式
code
miter: 尖角连接(默认)
round: 圆角连接
bevel: 斜角连接javascript
const joins = ['miter', 'round', 'bevel']
joins.forEach((join, index) => {
ctx.beginPath()
ctx.lineJoin = join
ctx.lineWidth = 20
ctx.moveTo(50 + index * 120, 50)
ctx.lineTo(100 + index * 120, 150)
ctx.lineTo(150 + index * 120, 50)
ctx.stroke()
})虚线
使用 setLineDash() 方法设置虚线样式。
javascript
// 设置虚线样式 [实线长度, 空白长度]
ctx.setLineDash([10, 5])
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(350, 50)
ctx.lineWidth = 2
ctx.stroke()
// 复杂虚线样式
ctx.setLineDash([20, 5, 5, 5])
ctx.beginPath()
ctx.moveTo(50, 100)
ctx.lineTo(350, 100)
ctx.stroke()
// 取消虚线
ctx.setLineDash([])
// 虚线动画
let offset = 0
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.setLineDash([10, 5])
ctx.lineDashOffset = offset
ctx.beginPath()
ctx.moveTo(50, 150)
ctx.lineTo(350, 150)
ctx.stroke()
offset++
if (offset > 15) offset = 0
requestAnimationFrame(animate)
}综合示例
绘制简单图形
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基础图形绘制</title>
</head>
<body>
<canvas id="myCanvas" width="500" height="400"></canvas>
<script>
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 绘制矩形
ctx.fillStyle = '#3498db'
ctx.fillRect(20, 20, 100, 80)
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.strokeRect(150, 20, 100, 80)
// 绘制三角形
ctx.beginPath()
ctx.moveTo(350, 20)
ctx.lineTo(300, 100)
ctx.lineTo(400, 100)
ctx.closePath()
ctx.fillStyle = '#2ecc71'
ctx.fill()
// 绘制五边形
ctx.beginPath()
const centerX = 100
const centerY = 200
const radius = 50
const sides = 5
for (let i = 0; i < sides; i++) {
const angle = (i * 2 * Math.PI / sides) - Math.PI / 2
const x = centerX + radius * Math.cos(angle)
const y = centerY + radius * Math.sin(angle)
if (i === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
}
ctx.closePath()
ctx.fillStyle = '#f39c12'
ctx.fill()
ctx.strokeStyle = '#d35400'
ctx.lineWidth = 2
ctx.stroke()
// 绘制星形
ctx.beginPath()
const starCenterX = 250
const starCenterY = 200
const outerRadius = 50
const innerRadius = 25
const points = 5
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? outerRadius : innerRadius
const angle = (i * Math.PI / points) - Math.PI / 2
const x = starCenterX + r * Math.cos(angle)
const y = starCenterY + r * Math.sin(angle)
if (i === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
}
ctx.closePath()
ctx.fillStyle = '#9b59b6'
ctx.fill()
// 绘制虚线矩形
ctx.setLineDash([10, 5])
ctx.strokeStyle = '#34495e'
ctx.lineWidth = 2
ctx.strokeRect(350, 150, 120, 100)
ctx.setLineDash([])
// 绘制不同线条样式
const caps = ['butt', 'round', 'square']
caps.forEach((cap, index) => {
ctx.beginPath()
ctx.lineCap = cap
ctx.lineWidth = 15
ctx.strokeStyle = '#1abc9c'
ctx.moveTo(50, 320 + index * 30)
ctx.lineTo(450, 320 + index * 30)
ctx.stroke()
})
</script>
</body>
</html>坐标系可视化
javascript
// 绘制坐标系网格
function drawGrid(ctx, width, height, gridSize) {
ctx.strokeStyle = '#e0e0e0'
ctx.lineWidth = 1
// 绘制垂直线
for (let x = 0; x <= width; x += gridSize) {
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x, height)
ctx.stroke()
}
// 绘制水平线
for (let y = 0; y <= height; y += gridSize) {
ctx.beginPath()
ctx.moveTo(0, y)
ctx.lineTo(width, y)
ctx.stroke()
}
// 绘制坐标轴
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
// X 轴
ctx.beginPath()
ctx.moveTo(0, height - 1)
ctx.lineTo(width, height - 1)
ctx.stroke()
// Y 轴
ctx.beginPath()
ctx.moveTo(1, 0)
ctx.lineTo(1, height)
ctx.stroke()
}常见问题
1. 图形显示模糊
原因: Canvas 实际尺寸与 CSS 显示尺寸不匹配。
解决方案:
javascript
// 设置高 DPI 支持
function setupCanvas(canvas) {
const dpr = window.devicePixelRatio || 1
const rect = canvas.getBoundingClientRect()
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
const ctx = canvas.getContext('2d')
ctx.scale(dpr, dpr)
canvas.style.width = rect.width + 'px'
canvas.style.height = rect.height + 'px'
return ctx
}2. 线条看起来有"毛边"
原因: 线条坐标在像素边界上。
解决方案: 将坐标偏移 0.5 像素。
javascript
// 绘制清晰的 1px 线条
ctx.moveTo(50.5, 10.5)
ctx.lineTo(200.5, 10.5)3. beginPath() 调用时机
问题: 什么时候需要调用 beginPath()?
答案: 每次绘制新的独立图形时都需要调用。以下情况需要调用:
- 开始绘制新图形
- 改变填充或描边样式前
- 需要独立的图形不受之前路径影响时
下一步学习
返回:Canvas 教程目录