引言
贝塞尔曲线(Bézier Curve)是现代图形设计中定义平滑曲线的核心工具,由法国数学家皮埃尔·贝塞尔(Pierre Bézier)在20世纪60年代用于汽车车身设计,如今已广泛应用于UI设计、动画路径、字体渲染等领域。
在Canvas中,我们可以通过原生API轻松绘制2阶和3阶贝塞尔曲线,甚至可以自行实现更高阶的曲线。本文将从原理到实战,带你彻底掌握Canvas中的贝塞尔曲线。
一、2阶贝塞尔曲线(Quadratic Bézier Curve)
1. 原理
2阶贝塞尔曲线是最简单的贝塞尔曲线,由1个起点(P0)、1个控制点(P1)和1个终点(P2)定义:
- 曲线从P0出发,最终到达P2;
- 曲线在P0处与线段
P0→P1相切,在P2处与线段P1→P2相切;
- 可以直观理解为:一根橡皮筋从P0拉到P2,中间被P1“顶”出一个平滑的弧度。
2. API语法
1
| ctx.quadraticCurveTo(cp1x, cp1y, x, y);
|
参数说明:
cp1x, cp1y:控制点(P1)的坐标;
x, y:终点(P2)的坐标;
- ⚠️ 注意:起点(P0)由上一个
moveTo()或路径点决定,无需在该方法中传入。
3. 代码示例(含辅助线)
为了更直观地理解曲线与控制点的关系,我们可以绘制辅助线(起点-控制点、控制点-终点的虚线):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d');
const p0 = { x: 20, y: 110 }; const p1 = { x: 230, y: 150 }; const p2 = { x: 250, y: 20 };
ctx.beginPath(); ctx.setLineDash([5, 5]); ctx.strokeStyle = '#999'; ctx.moveTo(p0.x, p0.y); ctx.lineTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke();
ctx.beginPath(); ctx.setLineDash([]); ctx.fillStyle = '#ff6b6b'; [ p0, p1, p2 ].forEach(point => { ctx.moveTo(point.x + 5, point.y); ctx.arc(point.x, point.y, 5, 0, Math.PI * 2); }); ctx.fill();
ctx.beginPath(); ctx.strokeStyle = '#1890ff'; ctx.lineWidth = 2; ctx.moveTo(p0.x, p0.y); ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y); ctx.stroke();
|
4. 应用场景
- 绘制抛物线(如投篮轨迹、物体下落曲线);
- 简单的圆角过渡(如按钮、卡片的圆角);
- 气泡对话框的尖角与主体的平滑连接。
二、3阶贝塞尔曲线(Cubic Bézier Curve)
1. 原理
3阶贝塞尔曲线是最常用的贝塞尔曲线,由1个起点(P0)、2个控制点(P1、P2)和1个终点(P3)定义:
- 曲线在P0处与线段
P0→P1相切,在P3处与线段P2→P3相切;
- 两个控制点分别控制曲线起点和终点的“弯曲方向”,比2阶曲线更灵活,能实现更复杂的平滑效果。
2. API语法
1
| ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
参数说明:
cp1x, cp1y:第一个控制点(P1)的坐标;
cp2x, cp2y:第二个控制点(P2)的坐标;
x, y:终点(P3)的坐标。
3. 代码示例(含辅助线)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d');
const p0 = { x: 50, y: 20 }; const p1 = { x: 230, y: 30 }; const p2 = { x: 150, y: 60 }; const p3 = { x: 50, y: 100 };
ctx.beginPath(); ctx.setLineDash([5, 5]); ctx.strokeStyle = '#999'; ctx.moveTo(p0.x, p0.y); ctx.lineTo(p1.x, p1.y); ctx.moveTo(p2.x, p2.y); ctx.lineTo(p3.x, p3.y); ctx.stroke();
ctx.beginPath(); ctx.setLineDash([]); ctx.fillStyle = '#ff6b6b'; [ p0, p1, p2, p3 ].forEach(point => { ctx.moveTo(point.x + 5, point.y); ctx.arc(point.x, point.y, 5, 0, Math.PI * 2); }); ctx.fill();
ctx.beginPath(); ctx.strokeStyle = '#1890ff'; ctx.lineWidth = 2; ctx.moveTo(p0.x, p0.y); ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); ctx.stroke();
|
4. 应用场景
- 字体轮廓渲染(如TrueType、OpenType字体);
- 复杂图标和插画的平滑曲线;
- 动画缓动路径(结合CSS
cubic-bezier() 或Canvas动画);
- 自然形态绘制(如河流、头发、植物藤蔓)。
三、N阶贝塞尔曲线:自定义实现
Canvas原生仅支持2阶和3阶贝塞尔曲线,若需要更高阶的曲线(如4阶、5阶),可以通过德卡斯特里奥算法(De Casteljau’s Algorithm)自行实现。
1. 德卡斯特里奥算法原理
该算法的核心是多次线性插值:
- 对于n个控制点,先在每两个相邻控制点之间进行线性插值,得到n-1个新点;
- 对新点重复上述过程,直到剩下1个点,该点即为贝塞尔曲线上的点;
- 遍历参数t(0 ≤ t ≤ 1),即可得到完整的曲线。
2. 简单实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
function drawN阶Bezier(ctx, points, step = 0.01) { ctx.beginPath(); for (let t = 0; t <= 1; t += step) { let tempPoints = [...points]; while (tempPoints.length > 1) { const newPoints = []; for (let i = 0; i < tempPoints.length - 1; i++) { const p0 = tempPoints[i]; const p1 = tempPoints[i + 1]; newPoints.push({ x: p0.x + t * (p1.x - p0.x), y: p0.y + t * (p1.y - p0.y) }); } tempPoints = newPoints; } const point = tempPoints[0]; if (t === 0) { ctx.moveTo(point.x, point.y); } else { ctx.lineTo(point.x, point.y); } } ctx.stroke(); }
const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.strokeStyle = '#1890ff'; ctx.lineWidth = 2;
const controlPoints = [ { x: 50, y: 200 }, { x: 100, y: 50 }, { x: 200, y: 300 }, { x: 300, y: 100 }, { x: 350, y: 200 } ];
drawN阶Bezier(ctx, controlPoints);
|
3. 推荐工具库
如果不想手动实现算法,可以使用开源库简化开发:
- bezierMaker.js:GitHub仓库,支持可视化调整N阶贝塞尔曲线的控制点。
四、实战技巧与注意事项
1. 辅助线调试
绘制曲线时,建议先画出控制点和辅助线(如本文示例中的虚线和实心圆),方便快速调整曲线形状,调试完成后再隐藏辅助线。
2. 性能优化
- 复杂场景下,优先使用3阶贝塞尔曲线而非多段2阶曲线,前者更平滑且计算量更小;
- 若需要重复绘制相同的贝塞尔曲线,可以使用
Path2D对象缓存路径:1 2 3 4 5
| const path = new Path2D(); path.moveTo(50, 20); path.bezierCurveTo(230, 30, 150, 60, 50, 100);
ctx.stroke(path);
|
3. 与SVG结合
SVG的<path>标签也支持贝塞尔曲线(Q/q表示2阶,C/c表示3阶),可以先用设计工具(如Figma、Sketch)导出SVG路径,再转换为Canvas代码使用。
五、拓展阅读
小结
贝塞尔曲线是Canvas绘图的“瑞士军刀”:
- 2阶曲线适合简单平滑过渡;
- 3阶曲线灵活度最高,是最常用的选择;
- N阶曲线可通过德卡斯特里奥算法实现,用于特殊复杂场景。
通过辅助线调试和工具库辅助,你可以快速将贝塞尔曲线应用到实际项目中,创造出流畅自然的图形效果!