Canvas 路径与曲线
本文档详细介绍 Canvas 的路径系统和曲线绘制方法,包括圆弧、贝塞尔曲线等高级路径绘制技术。
圆弧绘制 (arc)
arc() 方法是 Canvas 2D 上下文中用于绘制圆弧或圆形路径的重要方法。
方法签名
javascript
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
x | Number | 圆心的 X 坐标 |
y | Number | 圆心的 Y 坐标 |
radius | Number | 圆的半径 |
startAngle | Number | 起始角度(弧度) |
endAngle | Number | 结束角度(弧度) |
anticlockwise | Boolean | 可选,是否逆时针绘制(默认 false,顺时针) |
角度系统
code
-π/2 (270°)
│
│
π (180°) ─┼─ 0 (0°/360°)
│
│
π/2 (90°)
Canvas 角度系统:
- 0° 在右侧(X 轴正方向)
- 角度顺时针增加
- 使用弧度制,不是角度制角度转换公式:
javascript
// 角度转弧度
const radians = degrees * Math.PI / 180
// 弧度转角度
const degrees = radians * 180 / Math.PI基本用法
绘制完整圆形
javascript
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 绘制完整的红色圆形
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI * 2) // 从 0 到 2π(完整圆)
ctx.fillStyle = 'red'
ctx.fill()
ctx.strokeStyle = 'black'
ctx.lineWidth = 2
ctx.stroke()绘制扇形
javascript
// 绘制 90 度的蓝色扇形
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI / 2) // 从 0 到 π/2(90 度)
ctx.lineTo(100, 100) // 回到圆心
ctx.closePath()
ctx.fillStyle = 'blue'
ctx.fill()绘制半圆
javascript
// 绘制半圆
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI) // 从 0 到 π(180 度)
ctx.lineTo(100, 100)
ctx.closePath()
ctx.fillStyle = 'green'
ctx.fill()绘制弧形
javascript
// 绘制弧形(不闭合)
ctx.beginPath()
ctx.arc(100, 100, 50, Math.PI / 4, (Math.PI * 3) / 2) // 从 45° 到 270°
ctx.strokeStyle = 'purple'
ctx.lineWidth = 5
ctx.stroke()逆时针绘制
javascript
// 逆时针绘制圆弧
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI / 2, true) // 第 6 个参数为 true
ctx.strokeStyle = 'orange'
ctx.lineWidth = 3
ctx.stroke()完整示例:多种圆弧绘制
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>
<style>
canvas { display: block; margin: 10px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h2>完整圆形</h2>
<canvas id="circleCanvas" width="200" height="200"></canvas>
<h2>扇形</h2>
<canvas id="sectorCanvas" width="200" height="200"></canvas>
<h2>半圆</h2>
<canvas id="semicircleCanvas" width="200" height="200"></canvas>
<h2>弧形</h2>
<canvas id="arcCanvas" width="200" height="200"></canvas>
<script>
// 完整圆形
const ctx1 = document.getElementById('circleCanvas').getContext('2d')
ctx1.beginPath()
ctx1.arc(100, 100, 50, 0, Math.PI * 2)
ctx1.fillStyle = 'red'
ctx1.fill()
ctx1.strokeStyle = 'black'
ctx1.lineWidth = 2
ctx1.stroke()
// 扇形
const ctx2 = document.getElementById('sectorCanvas').getContext('2d')
ctx2.beginPath()
ctx2.arc(100, 100, 50, 0, Math.PI / 2)
ctx2.lineTo(100, 100)
ctx2.closePath()
ctx2.fillStyle = 'blue'
ctx2.fill()
// 半圆
const ctx3 = document.getElementById('semicircleCanvas').getContext('2d')
ctx3.beginPath()
ctx3.arc(100, 100, 50, 0, Math.PI)
ctx3.lineTo(100, 100)
ctx3.closePath()
ctx3.fillStyle = 'green'
ctx3.fill()
// 弧形
const ctx4 = document.getElementById('arcCanvas').getContext('2d')
ctx4.beginPath()
ctx4.arc(100, 100, 50, Math.PI / 4, (Math.PI * 3) / 2)
ctx4.strokeStyle = 'purple'
ctx4.lineWidth = 5
ctx4.stroke()
</script>
</body>
</html>实用应用示例
动态时钟
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="clockCanvas" width="300" height="300"></canvas>
<script>
const canvas = document.getElementById('clockCanvas')
const ctx = canvas.getContext('2d')
const radius = 120
function drawClockFace() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 绘制表盘
ctx.beginPath()
ctx.arc(150, 150, radius, 0, Math.PI * 2)
ctx.fillStyle = 'white'
ctx.fill()
ctx.strokeStyle = '#333'
ctx.lineWidth = 4
ctx.stroke()
// 绘制刻度
for (let i = 0; i < 12; i++) {
const angle = (i * Math.PI) / 6 - Math.PI / 2
ctx.beginPath()
ctx.moveTo(
150 + Math.cos(angle) * (radius - 15),
150 + Math.sin(angle) * (radius - 15)
)
ctx.lineTo(
150 + Math.cos(angle) * radius,
150 + Math.sin(angle) * radius
)
ctx.strokeStyle = i % 3 === 0 ? '#333' : '#999'
ctx.lineWidth = i % 3 === 0 ? 3 : 1
ctx.stroke()
}
}
function drawHands(hours, minutes, seconds) {
// 时针
const hourAngle = ((hours % 12) * Math.PI) / 6 + (minutes * Math.PI) / 360
drawHand(hourAngle, radius * 0.5, 6, '#333')
// 分针
const minuteAngle = (minutes * Math.PI) / 30
drawHand(minuteAngle, radius * 0.7, 4, '#666')
// 秒针
const secondAngle = (seconds * Math.PI) / 30
drawHand(secondAngle, radius * 0.9, 2, 'red')
}
function drawHand(angle, length, width, color) {
ctx.save()
ctx.translate(150, 150)
ctx.rotate(angle - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(length, 0)
ctx.lineWidth = width
ctx.strokeStyle = color
ctx.lineCap = 'round'
ctx.stroke()
ctx.restore()
}
function updateClock() {
const now = new Date()
drawClockFace()
drawHands(now.getHours(), now.getMinutes(), now.getSeconds())
requestAnimationFrame(updateClock)
}
updateClock()
</script>
</body>
</html>圆形进度条
javascript
function drawProgressCircle(canvasId, percent) {
const canvas = document.getElementById(canvasId)
const ctx = canvas.getContext('2d')
const radius = 50
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 背景圆
ctx.beginPath()
ctx.arc(100, 100, radius, 0, Math.PI * 2)
ctx.strokeStyle = '#eee'
ctx.lineWidth = 10
ctx.stroke()
// 进度圆弧
ctx.beginPath()
ctx.arc(
100, 100, radius,
-Math.PI / 2, // 从顶部开始
-Math.PI / 2 + percent * Math.PI * 2
)
ctx.strokeStyle = '#4CAF50'
ctx.lineWidth = 10
ctx.stroke()
// 百分比文字
ctx.fillStyle = '#333'
ctx.font = '16px Arial'
ctx.textAlign = 'center'
ctx.fillText(`${Math.round(percent * 100)}%`, 100, 105)
}
// 动画效果
let progress = 0
function animateProgress() {
if (progress <= 1) {
drawProgressCircle('progressCanvas', progress)
progress += 0.01
requestAnimationFrame(animateProgress)
}
}饼图
javascript
function drawPieChart(canvasId, data) {
const canvas = document.getElementById(canvasId)
const ctx = canvas.getContext('2d')
const centerX = 150
const centerY = 150
const radius = 100
const total = data.reduce((sum, item) => sum + item.value, 0)
let startAngle = -Math.PI / 2 // 从顶部开始
data.forEach((item) => {
const sliceAngle = (item.value / total) * Math.PI * 2
const endAngle = startAngle + sliceAngle
ctx.beginPath()
ctx.moveTo(centerX, centerY)
ctx.arc(centerX, centerY, radius, startAngle, endAngle)
ctx.closePath()
ctx.fillStyle = item.color
ctx.fill()
// 标签
const midAngle = startAngle + sliceAngle / 2
const labelX = centerX + Math.cos(midAngle) * 120
const labelY = centerY + Math.sin(midAngle) * 120
ctx.fillStyle = '#333'
ctx.font = '12px Arial'
ctx.textAlign = 'center'
ctx.fillText(item.label, labelX, labelY)
startAngle = endAngle
})
}
// 使用示例
drawPieChart('pieCanvas', [
{ label: '产品A', value: 30, color: '#3498db' },
{ label: '产品B', value: 25, color: '#e74c3c' },
{ label: '产品C', value: 20, color: '#2ecc71' },
{ label: '产品D', value: 15, color: '#f39c12' },
{ label: '其他', value: 10, color: '#9b59b6' }
])圆角路径 (arcTo)
arcTo() 方法用于绘制圆角路径,特别适合绘制圆角矩形和圆角多边形。
方法签名
javascript
ctx.arcTo(x1, y1, x2, y2, radius)参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
x1 | Number | 控制点 1 的 X 坐标 |
y1 | Number | 控制点 1 的 Y 坐标 |
x2 | Number | 控制点 2 的 X 坐标 |
y2 | Number | 控制点 2 的 Y 坐标 |
radius | Number | 圆角半径 |
工作原理
code
当前点 (x0, y0)
│
│
└───→ 控制点 1 (x1, y1)
│
│
└───→ 控制点 2 (x2, y2)
arcTo 会:
1. 从当前点画直线到控制点1的方向
2. 在控制点1处绘制圆角
3. 画直线到控制点2的方向基本用法
绘制圆角矩形
javascript
function drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.arcTo(x + width, y, x + width, y + height, radius)
ctx.arcTo(x + width, y + height, x, y + height, radius)
ctx.arcTo(x, y + height, x, y, radius)
ctx.arcTo(x, y, x + width, y, radius)
ctx.closePath()
ctx.fillStyle = 'lightblue'
ctx.fill()
ctx.strokeStyle = 'darkblue'
ctx.lineWidth = 2
ctx.stroke()
}
drawRoundedRect(ctx, 50, 50, 200, 100, 20)绘制圆角多边形
javascript
function drawRoundedPolygon(ctx, centerX, centerY, radius, sides, cornerRadius) {
ctx.beginPath()
for (let i = 0; i < sides; i++) {
const angle = (i * 2 * Math.PI) / sides - Math.PI / 2
const nextAngle = ((i + 1) * 2 * Math.PI) / sides - Math.PI / 2
const x1 = centerX + Math.cos(angle) * radius
const y1 = centerY + Math.sin(angle) * radius
const x2 = centerX + Math.cos(nextAngle) * radius
const y2 = centerY + Math.sin(nextAngle) * radius
if (i === 0) {
ctx.moveTo(x1, y1)
}
const midAngle = (angle + nextAngle) / 2
const ctrlX = centerX + Math.cos(midAngle) * (radius - cornerRadius)
const ctrlY = centerY + Math.sin(midAngle) * (radius - cornerRadius)
ctx.arcTo(ctrlX, ctrlY, x2, y2, cornerRadius)
}
ctx.closePath()
ctx.fillStyle = 'gold'
ctx.fill()
}
drawRoundedPolygon(ctx, 150, 150, 80, 5, 15)完整示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>arcTo 示例</title>
<style>
canvas { display: block; margin: 10px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h2>圆角矩形</h2>
<canvas id="roundedRectCanvas" width="250" height="150"></canvas>
<h2>圆角箭头</h2>
<canvas id="arrowCanvas" width="300" height="150"></canvas>
<script>
// 圆角矩形
const ctx1 = document.getElementById('roundedRectCanvas').getContext('2d')
const width = 200, height = 100, radius = 20
ctx1.beginPath()
ctx1.moveTo(25, 10)
ctx1.arcTo(25 + width, 10, 25 + width, 10 + height, radius)
ctx1.arcTo(25 + width, 10 + height, 25, 10 + height, radius)
ctx1.arcTo(25, 10 + height, 25, 10, radius)
ctx1.arcTo(25, 10, 25 + width, 10, radius)
ctx1.closePath()
ctx1.fillStyle = 'lightblue'
ctx1.fill()
ctx1.strokeStyle = 'darkblue'
ctx1.lineWidth = 2
ctx1.stroke()
// 圆角箭头
const ctx2 = document.getElementById('arrowCanvas').getContext('2d')
const startX = 50, startY = 75
const endX = 250, arrowSize = 20
ctx2.beginPath()
ctx2.moveTo(startX, startY - 10)
ctx2.lineTo(endX - arrowSize - 10, startY - 10)
ctx2.arcTo(endX - arrowSize, startY - 10, endX - arrowSize, startY, 10)
ctx2.lineTo(endX - arrowSize, startY)
ctx2.lineTo(endX, startY)
ctx2.lineTo(endX - arrowSize, startY + 15)
ctx2.lineTo(endX - arrowSize, startY + 10)
ctx2.arcTo(endX - arrowSize, startY + 10, startX, startY + 10, 10)
ctx2.lineTo(startX, startY + 10)
ctx2.closePath()
ctx2.fillStyle = 'gold'
ctx2.fill()
ctx2.strokeStyle = 'orange'
ctx2.lineWidth = 2
ctx2.stroke()
</script>
</body>
</html>贝塞尔曲线
贝塞尔曲线是一种强大的数学曲线,广泛应用于图形设计、动画和字体设计中。Canvas 支持二次和三次贝塞尔曲线。
二次贝塞尔曲线 (quadraticCurveTo)
使用一个控制点定义曲线形状。
方法签名
javascript
ctx.quadraticCurveTo(cp1x, cp1y, x, y)参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
cp1x | Number | 控制点的 X 坐标 |
cp1y | Number | 控制点的 Y 坐标 |
x | Number | 结束点的 X 坐标 |
y | Number | 结束点的 Y 坐标 |
曲线原理
code
控制点 (cp1x, cp1y)
●
╱ ╲
╱ ╲
╱ ╲
●───────●
起点 终点
(x0,y0) (x,y)
曲线被控制点"拉向"但不通过控制点基本用法
javascript
ctx.beginPath()
ctx.moveTo(50, 200) // 起点
ctx.quadraticCurveTo(150, 50, 250, 200) // 控制点 + 终点
ctx.strokeStyle = 'blue'
ctx.lineWidth = 3
ctx.stroke()绘制气泡对话框
javascript
function drawSpeechBubble(ctx, x, y, width, height, radius) {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius - 20)
ctx.quadraticCurveTo(x + width, y + height - 20, x + width - radius, y + height - 20)
ctx.lineTo(x + width * 0.7, y + height - 20)
ctx.lineTo(x + width * 0.6, y + height)
ctx.lineTo(x + width * 0.5, y + height - 20)
ctx.lineTo(x + radius, y + height - 20)
ctx.quadraticCurveTo(x, y + height - 20, x, y + height - radius - 20)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
ctx.closePath()
ctx.fillStyle = 'white'
ctx.fill()
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.stroke()
}绘制花瓣
javascript
function drawPetal(ctx, x, y, length, width) {
ctx.beginPath()
ctx.moveTo(x, y)
ctx.quadraticCurveTo(x - width, y + length / 2, x, y + length)
ctx.quadraticCurveTo(x + width, y + length / 2, x, y)
ctx.closePath()
ctx.fillStyle = 'pink'
ctx.fill()
ctx.strokeStyle = 'hotpink'
ctx.stroke()
}
// 绘制花朵
const petalCount = 6
for (let i = 0; i < petalCount; i++) {
const angle = (i / petalCount) * Math.PI * 2
ctx.save()
ctx.translate(150, 150)
ctx.rotate(angle)
drawPetal(ctx, 0, 0, 60, 20)
ctx.restore()
}三次贝塞尔曲线 (bezierCurveTo)
使用两个控制点定义曲线形状,提供更精确的控制。
方法签名
javascript
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
cp1x | Number | 控制点 1 的 X 坐标 |
cp1y | Number | 控制点 1 的 Y 坐标 |
cp2x | Number | 控制点 2 的 X 坐标 |
cp2y | Number | 控制点 2 的 Y 坐标 |
x | Number | 结束点的 X 坐标 |
y | Number | 结束点的 Y 坐标 |
曲线原理
code
控制点1 控制点2
●──────────●
╱ ╲
╱ ╲
●────────────────●
起点 终点
曲线被两个控制点"拉向",产生 S 形曲线基本用法
javascript
ctx.beginPath()
ctx.moveTo(50, 200) // 起点
ctx.bezierCurveTo(100, 50, 200, 350, 250, 200) // 控制点1 + 控制点2 + 终点
ctx.strokeStyle = 'red'
ctx.lineWidth = 3
ctx.stroke()绘制心形
javascript
function drawHeart(ctx, x, y, size) {
ctx.beginPath()
ctx.moveTo(x, y + size / 4)
// 左半边
ctx.bezierCurveTo(
x, y,
x - size / 2, y,
x - size / 2, y + size / 4
)
ctx.bezierCurveTo(
x - size / 2, y + size / 2,
x, y + size * 0.75,
x, y + size
)
// 右半边
ctx.bezierCurveTo(
x, y + size * 0.75,
x + size / 2, y + size / 2,
x + size / 2, y + size / 4
)
ctx.bezierCurveTo(
x + size / 2, y,
x, y,
x, y + size / 4
)
ctx.fillStyle = '#e74c3c'
ctx.fill()
}
drawHeart(ctx, 150, 50, 120)绘制波浪
javascript
function drawWave(ctx, startX, startY, width, height, segments) {
ctx.beginPath()
ctx.moveTo(startX, startY)
const segmentWidth = width / segments
let currentX = startX
for (let i = 0; i < segments; i++) {
const direction = i % 2 === 0 ? -1 : 1
ctx.bezierCurveTo(
currentX + segmentWidth * 0.25, startY + height * direction,
currentX + segmentWidth * 0.75, startY + height * direction,
currentX + segmentWidth, startY
)
currentX += segmentWidth
}
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 3
ctx.stroke()
}
drawWave(ctx, 50, 150, 300, 50, 4)二次 vs 三次贝塞尔曲线对比
| 特性 | 二次贝塞尔 | 三次贝塞尔 |
|---|---|---|
| 控制点数量 | 1 个 | 2 个 |
| 控制精度 | 较低 | 较高 |
| 计算复杂度 | 较低 | 较高 |
| 适用场景 | 简单曲线、弧形 | 复杂曲线、S 形 |
复杂路径应用
绘制对话框
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="dialogCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('dialogCanvas')
const ctx = canvas.getContext('2d')
function drawDialog(x, y, width, height, text) {
const radius = 15
const tailWidth = 20
const tailHeight = 15
ctx.beginPath()
// 右上角
ctx.moveTo(x + width - radius, y)
ctx.arcTo(x + width, y, x + width, y + radius, radius)
// 右边
ctx.lineTo(x + width, y + height - radius)
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius)
// 底边(带尾巴)
ctx.lineTo(x + width * 0.7, y + height)
ctx.lineTo(x + width * 0.6, y + height + tailHeight)
ctx.lineTo(x + width * 0.5, y + height)
ctx.lineTo(x + radius, y + height)
ctx.arcTo(x, y + height, x, y + height - radius, radius)
// 左边
ctx.lineTo(x, y + radius)
ctx.arcTo(x, y, x + radius, y, radius)
ctx.closePath()
// 填充渐变
const gradient = ctx.createLinearGradient(x, y, x, y + height)
gradient.addColorStop(0, '#ffffff')
gradient.addColorStop(1, '#f0f0f0')
ctx.fillStyle = gradient
ctx.fill()
ctx.strokeStyle = '#ddd'
ctx.lineWidth = 2
ctx.stroke()
// 文字
ctx.fillStyle = '#333'
ctx.font = '14px Arial'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, x + width / 2, y + height / 2)
}
drawDialog(50, 50, 300, 150, '这是一个对话框示例')
</script>
</body>
</html>绘制自定义形状
javascript
// 绘制星形
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
let rot = Math.PI / 2 * 3
let x = cx
let y = cy
const step = Math.PI / spikes
ctx.beginPath()
ctx.moveTo(cx, cy - outerRadius)
for (let i = 0; i < spikes; i++) {
x = cx + Math.cos(rot) * outerRadius
y = cy + Math.sin(rot) * outerRadius
ctx.lineTo(x, y)
rot += step
x = cx + Math.cos(rot) * innerRadius
y = cy + Math.sin(rot) * innerRadius
ctx.lineTo(x, y)
rot += step
}
ctx.lineTo(cx, cy - outerRadius)
ctx.closePath()
ctx.fillStyle = '#f39c12'
ctx.fill()
ctx.strokeStyle = '#e67e22'
ctx.lineWidth = 2
ctx.stroke()
}
drawStar(ctx, 200, 150, 5, 80, 40)
// 绘制齿轮
function drawGear(ctx, cx, cy, teeth, outerRadius, innerRadius) {
const toothDepth = (outerRadius - innerRadius) / 2
const toothWidth = (Math.PI * 2) / teeth / 2
ctx.beginPath()
for (let i = 0; i < teeth; i++) {
const angle = (i * Math.PI * 2) / teeth
// 齿根
ctx.lineTo(
cx + Math.cos(angle) * innerRadius,
cy + Math.sin(angle) * innerRadius
)
// 齿上升
ctx.lineTo(
cx + Math.cos(angle + toothWidth * 0.3) * outerRadius,
cy + Math.sin(angle + toothWidth * 0.3) * outerRadius
)
// 齿顶
ctx.lineTo(
cx + Math.cos(angle + toothWidth * 0.7) * outerRadius,
cy + Math.sin(angle + toothWidth * 0.7) * outerRadius
)
// 齿下降
ctx.lineTo(
cx + Math.cos(angle + toothWidth) * innerRadius,
cy + Math.sin(angle + toothWidth) * innerRadius
)
}
ctx.closePath()
ctx.fillStyle = '#7f8c8d'
ctx.fill()
ctx.strokeStyle = '#2c3e50'
ctx.lineWidth = 2
ctx.stroke()
}
drawGear(ctx, 350, 150, 12, 60, 40)常见问题
1. 弧度与角度转换错误
问题: 使用角度而非弧度导致绘制错误。
解决方案: 始终记住转换公式。
javascript
// 工具函数
const toRadians = degrees => degrees * Math.PI / 180
const toDegrees = radians => radians * 180 / Math.PI
// 使用
ctx.arc(100, 100, 50, toRadians(0), toRadians(90))2. 贝塞尔曲线控制点位置不理想
问题: 曲线形状不符合预期。
解决方案: 绘制控制点和辅助线来调试。
javascript
function drawBezierWithControls(ctx, x0, y0, cp1x, cp1y, cp2x, cp2y, x, y) {
// 绘制辅助线
ctx.beginPath()
ctx.moveTo(x0, y0)
ctx.lineTo(cp1x, cp1y)
ctx.lineTo(cp2x, cp2y)
ctx.lineTo(x, y)
ctx.strokeStyle = '#ccc'
ctx.lineWidth = 1
ctx.setLineDash([5, 5])
ctx.stroke()
ctx.setLineDash([])
// 绘制控制点
ctx.fillStyle = 'red'
ctx.beginPath()
ctx.arc(cp1x, cp1y, 5, 0, Math.PI * 2)
ctx.fill()
ctx.fillStyle = 'blue'
ctx.beginPath()
ctx.arc(cp2x, cp2y, 5, 0, Math.PI * 2)
ctx.fill()
// 绘制曲线
ctx.beginPath()
ctx.moveTo(x0, y0)
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
ctx.strokeStyle = 'black'
ctx.lineWidth = 2
ctx.stroke()
}3. 路径未正确闭合
问题: 填充时出现意外的连接线。
解决方案: 确保正确使用 closePath() 或手动连接起点。
javascript
// ❌ 错误示例
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI * 2)
ctx.fill() // 可能出现问题
// ✅ 正确示例
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI * 2)
ctx.closePath() // 确保闭合
ctx.fill()下一步学习
返回:Canvas 教程目录 | 上一篇:基础绘图