pin_drop当前位置:知识文库 ❯ 图文

多媒体:贝塞尔曲线 - 完整教程与代码示例

一、教程简介

贝塞尔曲线是计算机图形学中最常用的曲线类型,广泛应用于矢量绘图、字体设计、动画路径等领域。Canvas 提供了两种贝塞尔曲线绘制方法:quadraticCurveTo 绘制二次贝塞尔曲线(1个控制点),bezierCurveTo 绘制三次贝塞尔曲线(2个控制点)。本教程将详细讲解贝塞尔曲线的原理、绘制方法和实际应用。


二、核心概念

二次贝塞尔曲线(Quadratic Bezier)

二次贝塞尔曲线由三个点定义:

  • 起点(P0):当前路径点

  • 控制点(P1):决定曲线弯曲方向和程度

  • 终点(P2):曲线结束点

曲线上的点通过参数 t(0~1)计算:

代码示例

B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2

三次贝塞尔曲线(Cubic Bezier)

三次贝塞尔曲线由四个点定义:

  • 起点(P0):当前路径点

  • 控制点1(P1):影响曲线起始方向

  • 控制点2(P2):影响曲线结束方向

  • 终点(P3):曲线结束点

曲线上的点通过参数 t(0~1)计算:

代码示例

B(t) = (1-t)^3 * P0 + 3(1-t)^2t * P1 + 3(1-t)t^2 * P2 + t^3 * P3

控制点的作用

控制点不在曲线上,但它们像磁铁一样"吸引"曲线,控制曲线的弯曲方向和程度。控制点离曲线越远,曲线弯曲越明显。


三、语法与用法

quadraticCurveTo - 二次贝塞尔曲线

代码示例

ctx.quadraticCurveTo(cpx, cpy, x, y);
参数 说明
cpx, cpy 控制点坐标
x, y 终点坐标

起点为当前路径点。

bezierCurveTo - 三次贝塞尔曲线

代码示例

ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
参数 说明
cp1x, cp1y 第一个控制点坐标
cp2x, cp2y 第二个控制点坐标
x, y 终点坐标

起点为当前路径点。


四、代码示例

示例1:贝塞尔曲线原理与对比

以下代码演示了二次贝塞尔曲线和三次贝塞尔曲线的原理对比,以及控制点位置对曲线形状的影响:

代码示例

<!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>
    body {
      margin: 0;
      padding: 20px;
      background: #0d1117;
      color: #c9d1d9;
      font-family: 'Segoe UI', sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    canvas {
      border: 1px solid #30363d;
      border-radius: 8px;
      background: #161b22;
    }
  </style>
</head>
<body>
  <h1>贝塞尔曲线原理与对比</h1>
  <canvas id="myCanvas" width="750" height="500"></canvas>

  <script>
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');

    // === 二次贝塞尔曲线 ===
    ctx.fillStyle = '#c9d1d9';
    ctx.font = 'bold 14px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('二次贝塞尔曲线 (quadraticCurveTo)', 200, 25);

    const q = {
      p0: { x: 50, y: 200 },
      cp: { x: 200, y: 50 },
      p2: { x: 350, y: 200 }
    };

    // 辅助线
    ctx.beginPath();
    ctx.moveTo(q.p0.x, q.p0.y);
    ctx.lineTo(q.cp.x, q.cp.y);
    ctx.lineTo(q.p2.x, q.p2.y);
    ctx.strokeStyle = '#30363d';
    ctx.lineWidth = 1;
    ctx.setLineDash([4, 4]);
    ctx.stroke();
    ctx.setLineDash([]);

    // 曲线
    ctx.beginPath();
    ctx.moveTo(q.p0.x, q.p0.y);
    ctx.quadraticCurveTo(q.cp.x, q.cp.y, q.p2.x, q.p2.y);
    ctx.strokeStyle = '#58a6ff';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 标注点
    [[q.p0, 'P0 起点'], [q.cp, 'P1 控制点'], [q.p2, 'P2 终点']].forEach(([p, label]) => {
      ctx.beginPath();
      ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
      ctx.fillStyle = label.includes('控制') ? '#e94560' : '#3fb950';
      ctx.fill();
      ctx.fillStyle = '#8b949e';
      ctx.font = '11px sans-serif';
      ctx.fillText(label, p.x, p.y - 15);
    });

    // 绘制 t 值动画点
    for (let t = 0; t <= 1; t += 0.1) {
      const x = (1-t)*(1-t)*q.p0.x + 2*(1-t)*t*q.cp.x + t*t*q.p2.x;
      const y = (1-t)*(1-t)*q.p0.y + 2*(1-t)*t*q.cp.y + t*t*q.p2.y;
      ctx.beginPath();
      ctx.arc(x, y, 2, 0, Math.PI * 2);
      ctx.fillStyle = '#58a6ff';
      ctx.fill();
    }

    // === 三次贝塞尔曲线 ===
    ctx.fillStyle = '#c9d1d9';
    ctx.font = 'bold 14px sans-serif';
    ctx.fillText('三次贝塞尔曲线 (bezierCurveTo)', 575, 25);

    const c = {
      p0: { x: 425, y: 200 },
      cp1: { x: 475, y: 50 },
      cp2: { x: 675, y: 350 },
      p3: { x: 725, y: 200 }
    };

    // 辅助线
    ctx.beginPath();
    ctx.moveTo(c.p0.x, c.p0.y);
    ctx.lineTo(c.cp1.x, c.cp1.y);
    ctx.moveTo(c.cp2.x, c.cp2.y);
    ctx.lineTo(c.p3.x, c.p3.y);
    ctx.strokeStyle = '#30363d';
    ctx.lineWidth = 1;
    ctx.setLineDash([4, 4]);
    ctx.stroke();
    ctx.setLineDash([]);

    // 曲线
    ctx.beginPath();
    ctx.moveTo(c.p0.x, c.p0.y);
    ctx.bezierCurveTo(c.cp1.x, c.cp1.y, c.cp2.x, c.cp2.y, c.p3.x, c.p3.y);
    ctx.strokeStyle = '#3fb950';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 标注点
    [[c.p0, 'P0 起点'], [c.cp1, 'P1 控制点1'], [c.cp2, 'P2 控制点2'], [c.p3, 'P3 终点']].forEach(([p, label]) => {
      ctx.beginPath();
      ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
      ctx.fillStyle = label.includes('控制') ? '#e94560' : '#3fb950';
      ctx.fill();
      ctx.fillStyle = '#8b949e';
      ctx.font = '11px sans-serif';
      ctx.fillText(label, p.x, p.y - 15);
    });

    // 绘制 t 值动画点
    for (let t = 0; t <= 1; t += 0.05) {
      const mt = 1 - t;
      const x = mt*mt*mt*c.p0.x + 3*mt*mt*t*c.cp1.x + 3*mt*t*t*c.cp2.x + t*t*t*c.p3.x;
      const y = mt*mt*mt*c.p0.y + 3*mt*mt*t*c.cp1.y + 3*mt*t*t*c.cp2.y + t*t*t*c.p3.y;
      ctx.beginPath();
      ctx.arc(x, y, 2, 0, Math.PI * 2);
      ctx.fillStyle = '#3fb950';
      ctx.fill();
    }

    // === 不同控制点位置的效果 ===
    ctx.fillStyle = '#c9d1d9';
    ctx.font = 'bold 14px sans-serif';
    ctx.fillText('控制点位置对曲线的影响', 375, 260);

    // 二次贝塞尔 - 控制点高低
    const heights = [80, 130, 180, 230];
    const colors = ['#58a6ff', '#3fb950', '#f8b500', '#e94560'];

    heights.forEach((h, i) => {
      ctx.beginPath();
      ctx.moveTo(50, 390);
      ctx.quadraticCurveTo(200, h, 350, 390);
      ctx.strokeStyle = colors[i];
      ctx.lineWidth = 2;
      ctx.stroke();

      // 控制点标记
      ctx.beginPath();
      ctx.arc(200, h, 3, 0, Math.PI * 2);
      ctx.fillStyle = colors[i];
      ctx.fill();
    });

    ctx.fillStyle = '#8b949e';
    ctx.font = '11px sans-serif';
    ctx.fillText('控制点越高曲线弯曲越大', 200, 470);

    // 三次贝塞尔 - S 形曲线
    ctx.beginPath();
    ctx.moveTo(420, 390);
    ctx.bezierCurveTo(470, 280, 630, 480, 680, 390);
    ctx.strokeStyle = '#d2a8ff';
    ctx.lineWidth = 3;
    ctx.stroke();

    ctx.fillStyle = '#8b949e';
    ctx.font = '11px sans-serif';
    ctx.fillText('三次贝塞尔可绘制 S 形', 550, 470);
  </script>
</body>
</html>

示例2:贝塞尔曲线实际应用

以下代码展示了贝塞尔曲线在实际场景中的应用,包括心形、波浪线、水滴、平滑曲线图表和花瓣图案的绘制:

代码示例

<!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>
    body {
      margin: 0;
      padding: 20px;
      background: #1a1a2e;
      color: #eee;
      font-family: 'Segoe UI', sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    canvas {
      border: 1px solid #333;
      border-radius: 8px;
      background: #16213e;
    }
  </style>
</head>
<body>
  <h1>贝塞尔曲线实际应用</h1>
  <canvas id="myCanvas" width="700" height="500"></canvas>

  <script>
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');

    // === 1. 绘制心形 ===
    ctx.fillStyle = '#eee';
    ctx.font = 'bold 14px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('心形', 120, 30);

    ctx.beginPath();
    ctx.moveTo(120, 130);
    ctx.bezierCurveTo(120, 90, 60, 70, 60, 110);
    ctx.bezierCurveTo(60, 140, 120, 170, 120, 190);
    ctx.bezierCurveTo(120, 170, 180, 140, 180, 110);
    ctx.bezierCurveTo(180, 70, 120, 90, 120, 130);
    ctx.fillStyle = 'rgba(233, 69, 96, 0.5)';
    ctx.fill();
    ctx.strokeStyle = '#e94560';
    ctx.lineWidth = 2;
    ctx.stroke();

    // === 2. 绘制波浪线 ===
    ctx.fillStyle = '#eee';
    ctx.fillText('波浪线', 350, 30);

    ctx.beginPath();
    ctx.moveTo(230, 120);
    for (let i = 0; i < 4; i++) {
      const x = 230 + i * 60;
      ctx.quadraticCurveTo(x + 15, 80, x + 30, 120);
      ctx.quadraticCurveTo(x + 45, 160, x + 60, 120);
    }
    ctx.strokeStyle = '#58a6ff';
    ctx.lineWidth = 3;
    ctx.stroke();

    // === 3. 绘制水滴 ===
    ctx.fillStyle = '#eee';
    ctx.fillText('水滴', 580, 30);

    ctx.beginPath();
    ctx.moveTo(580, 60);
    ctx.quadraticCurveTo(620, 110, 620, 140);
    ctx.arc(590, 140, 30, 0, Math.PI, false);
    ctx.quadraticCurveTo(540, 110, 580, 60);
    ctx.fillStyle = 'rgba(88, 166, 255, 0.4)';
    ctx.fill();
    ctx.strokeStyle = '#58a6ff';
    ctx.lineWidth = 2;
    ctx.stroke();

    // === 4. 绘制平滑曲线图表 ===
    ctx.fillStyle = '#eee';
    ctx.font = 'bold 14px sans-serif';
    ctx.fillText('平滑曲线图表', 350, 220);

    const dataPoints = [
      { x: 80, y: 350 }, { x: 160, y: 300 }, { x: 240, y: 320 },
      { x: 320, y: 260 }, { x: 400, y: 280 }, { x: 480, y: 240 },
      { x: 560, y: 270 }, { x: 640, y: 230 }
    ];

    // 绘制网格
    ctx.strokeStyle = '#1a2a3e';
    ctx.lineWidth = 1;
    for (let y = 230; y <= 370; y += 30) {
      ctx.beginPath();
      ctx.moveTo(60, y);
      ctx.lineTo(660, y);
      ctx.stroke();
    }

    // 绘制平滑曲线
    ctx.beginPath();
    ctx.moveTo(dataPoints[0].x, dataPoints[0].y);

    for (let i = 0; i < dataPoints.length - 1; i++) {
      const curr = dataPoints[i];
      const next = dataPoints[i + 1];
      const cpx = (curr.x + next.x) / 2;
      ctx.quadraticCurveTo(curr.x, curr.y, cpx, (curr.y + next.y) / 2);
    }
    const last = dataPoints[dataPoints.length - 1];
    ctx.lineTo(last.x, last.y);
    ctx.strokeStyle = '#3fb950';
    ctx.lineWidth = 3;
    ctx.stroke();

    // 填充区域
    ctx.lineTo(last.x, 370);
    ctx.lineTo(dataPoints[0].x, 370);
    ctx.closePath();
    ctx.fillStyle = 'rgba(63, 185, 80, 0.1)';
    ctx.fill();

    // 数据点
    dataPoints.forEach(p => {
      ctx.beginPath();
      ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
      ctx.fillStyle = '#3fb950';
      ctx.fill();
      ctx.strokeStyle = '#16213e';
      ctx.lineWidth = 2;
      ctx.stroke();
    });

    // === 5. 绘制花瓣 ===
    ctx.fillStyle = '#eee';
    ctx.font = 'bold 14px sans-serif';
    ctx.fillText('花瓣图案', 350, 400);

    const petalCount = 6;
    const petalCx = 350, petalCy = 440;

    for (let i = 0; i < petalCount; i++) {
      const angle = (i / petalCount) * Math.PI * 2;
      const nextAngle = ((i + 1) / petalCount) * Math.PI * 2;

      ctx.beginPath();
      ctx.moveTo(petalCx, petalCy);

      const tipX = petalCx + 40 * Math.cos(angle - 0.3);
      const tipY = petalCy + 40 * Math.sin(angle - 0.3);
      const tipX2 = petalCx + 40 * Math.cos(angle + 0.3);
      const tipY2 = petalCy + 40 * Math.sin(angle + 0.3);

      const cp1x = petalCx + 50 * Math.cos(angle - 0.15);
      const cp1y = petalCy + 50 * Math.sin(angle - 0.15);
      const cp2x = petalCx + 50 * Math.cos(angle + 0.15);
      const cp2y = petalCy + 50 * Math.sin(angle + 0.15);

      ctx.quadraticCurveTo(cp1x, cp1y, petalCx + 55 * Math.cos(angle), petalCy + 55 * Math.sin(angle));
      ctx.quadraticCurveTo(cp2x, cp2y, petalCx, petalCy);

      ctx.fillStyle = `hsla(${i * 60}, 70%, 60%, 0.4)`;
      ctx.fill();
      ctx.strokeStyle = `hsla(${i * 60}, 70%, 60%, 0.8)`;
      ctx.lineWidth = 1;
      ctx.stroke();
    }

    // 花心
    ctx.beginPath();
    ctx.arc(petalCx, petalCy, 8, 0, Math.PI * 2);
    ctx.fillStyle = '#f8b500';
    ctx.fill();
  </script>
</body>
</html>

五、浏览器兼容性

方法 Chrome Firefox Safari Edge IE
quadraticCurveTo 1+ 1.5+ 2+ 12+ 9+
bezierCurveTo 1+ 1.5+ 2+ 12+ 9+

六、注意事项与最佳实践

1. 起点 = 当前路径点

代码示例

// 必须先设置起点
ctx.beginPath();
ctx.moveTo(50, 100); // 起点
ctx.quadraticCurveTo(150, 50, 250, 100); // 控制点 + 终点
ctx.stroke();

2. 连续绘制平滑曲线

代码示例

// 使用中点法绘制平滑曲线
function drawSmoothCurve(ctx, points) {
  if (points.length < 2) return;

  ctx.beginPath();
  ctx.moveTo(points[0][0], points[0][1]);

  if (points.length === 2) {
    ctx.lineTo(points[1][0], points[1][1]);
  } else {
    // 第一段使用直线到第一个中点
    let midX = (points[0][0] + points[1][0]) / 2;
    let midY = (points[0][1] + points[1][1]) / 2;
    ctx.lineTo(midX, midY);

    // 中间段使用二次贝塞尔曲线
    for (let i = 1; i < points.length - 1; i++) {
      const nextMidX = (points[i][0] + points[i + 1][0]) / 2;
      const nextMidY = (points[i][1] + points[i + 1][1]) / 2;
      ctx.quadraticCurveTo(
        points[i][0], points[i][1],
        nextMidX, nextMidY
      );
    }

    // 最后一段
    const last = points[points.length - 1];
    ctx.lineTo(last[0], last[1]);
  }

  ctx.stroke();
}

3. 控制点的选择

  • 控制点越远,曲线弯曲越大

  • 控制点方向决定曲线弯曲方向

  • 两个控制点之间的距离影响曲线的"张力"

4. 二次 vs 三次的选择

  • 简单弧线:使用二次贝塞尔曲线

  • S 形曲线:使用三次贝塞尔曲线

  • 平滑连接:二次贝塞尔更易控制


七、代码规范示例

代码示例

// 推荐:封装二次贝塞尔曲线绘制
function drawQuadratic(ctx, startX, startY, cpX, cpY, endX, endY, options) {
  const {
    stroke = '#000',
    fill = null,
    lineWidth = 1,
    showControl = false
  } = options || {};

  ctx.beginPath();
  ctx.moveTo(startX, startY);
  ctx.quadraticCurveTo(cpX, cpY, endX, endY);

  if (fill) {
    ctx.fillStyle = fill;
    ctx.fill();
  }
  if (stroke) {
    ctx.strokeStyle = stroke;
    ctx.lineWidth = lineWidth;
    ctx.stroke();
  }

  // 可选:显示控制点
  if (showControl) {
    ctx.setLineDash([4, 4]);
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(cpX, cpY);
    ctx.lineTo(endX, endY);
    ctx.strokeStyle = '#999';
    ctx.lineWidth = 1;
    ctx.stroke();
    ctx.setLineDash([]);

    ctx.beginPath();
    ctx.arc(cpX, cpY, 4, 0, Math.PI * 2);
    ctx.fillStyle = '#e94560';
    ctx.fill();
  }
}

// 推荐:封装三次贝塞尔曲线绘制
function drawCubic(ctx, startX, startY, cp1X, cp1Y, cp2X, cp2Y, endX, endY, options) {
  const {
    stroke = '#000',
    fill = null,
    lineWidth = 1
  } = options || {};

  ctx.beginPath();
  ctx.moveTo(startX, startY);
  ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, endX, endY);

  if (fill) {
    ctx.fillStyle = fill;
    ctx.fill();
  }
  if (stroke) {
    ctx.strokeStyle = stroke;
    ctx.lineWidth = lineWidth;
    ctx.stroke();
  }
}

// 使用示例
drawQuadratic(ctx, 50, 100, 150, 50, 250, 100, {
  stroke: '#58a6ff',
  lineWidth: 2,
  showControl: true
});

drawCubic(ctx, 50, 200, 100, 100, 200, 300, 250, 200, {
  stroke: '#3fb950',
  lineWidth: 2
});

八、常见问题与解决方案

Q1:如何让多段贝塞尔曲线平滑连接?

解决方案:确保连接点处的切线方向一致:

代码示例

// 方法:控制点与连接点共线
// P1 的第二个控制点、连接点、P2 的第一个控制点应在同一直线上
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.bezierCurveTo(100, 50, 150, 50, 200, 100); // 第一段
ctx.bezierCurveTo(250, 150, 300, 150, 350, 100); // 第二段
// 150,50 -> 200,100 -> 250,150 大致共线,曲线平滑
ctx.stroke();

Q2:如何计算贝塞尔曲线上的点?

解决方案

代码示例

// 二次贝塞尔曲线上的点
function getQuadraticPoint(p0, p1, p2, t) {
  const mt = 1 - t;
  return {
    x: mt * mt * p0.x + 2 * mt * t * p1.x + t * t * p2.x,
    y: mt * mt * p0.y + 2 * mt * t * p1.y + t * t * p2.y
  };
}

// 三次贝塞尔曲线上的点
function getCubicPoint(p0, p1, p2, p3, t) {
  const mt = 1 - t;
  return {
    x: mt*mt*mt*p0.x + 3*mt*mt*t*p1.x + 3*mt*t*t*p2.x + t*t*t*p3.x,
    y: mt*mt*mt*p0.y + 3*mt*mt*t*p1.y + 3*mt*t*t*p2.y + t*t*t*p3.y
  };
}

Q3:如何沿贝塞尔曲线移动物体?

解决方案

代码示例

let t = 0;
const p0 = { x: 50, y: 200 };
const p1 = { x: 200, y: 50 };
const p2 = { x: 350, y: 200 };

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 绘制曲线
  ctx.beginPath();
  ctx.moveTo(p0.x, p0.y);
  ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
  ctx.strokeStyle = '#58a6ff';
  ctx.lineWidth = 2;
  ctx.stroke();

  // 计算物体位置
  const pos = getQuadraticPoint(p0, p1, p2, t);
  ctx.beginPath();
  ctx.arc(pos.x, pos.y, 8, 0, Math.PI * 2);
  ctx.fillStyle = '#e94560';
  ctx.fill();

  t += 0.005;
  if (t > 1) t = 0;

  requestAnimationFrame(animate);
}
animate();

Q4:如何绘制等距的贝塞尔曲线点?

原因:参数 t 等分不等于弧长等分。

解决方案:使用近似方法或查表法:

代码示例

// 简单近似:细分 t 值,累积弧长
function getEquidistantPoints(p0, p1, p2, numPoints) {
  const points = [];
  const steps = 200; // 细分步数
  const allPoints = [];

  for (let i = 0; i <= steps; i++) {
    const t = i / steps;
    allPoints.push(getQuadraticPoint(p0, p1, p2, t));
  }

  // 计算总弧长
  let totalLen = 0;
  const lengths = [0];
  for (let i = 1; i < allPoints.length; i++) {
    const dx = allPoints[i].x - allPoints[i-1].x;
    const dy = allPoints[i].y - allPoints[i-1].y;
    totalLen += Math.sqrt(dx * dx + dy * dy);
    lengths.push(totalLen);
  }

  // 等距取点
  for (let i = 0; i < numPoints; i++) {
    const targetLen = (i / (numPoints - 1)) * totalLen;
    let idx = 0;
    while (idx < lengths.length - 1 && lengths[idx + 1] < targetLen) idx++;
    points.push(allPoints[idx]);
  }

  return points;
}

Q5:二次和三次贝塞尔曲线如何选择?

解决方案

场景 推荐 原因
简单弧线 二次 参数少,易于控制
S 形曲线 三次 需要两个控制点
圆角 二次 单一弯曲方向
字体轮廓 三次 精确控制曲线形状
平滑图表 二次 中点法简单高效

九、总结

贝塞尔曲线是 Canvas 绘制复杂曲线的核心工具,关键要点:

  • quadraticCurveTo:二次贝塞尔曲线,1个控制点,适合简单弧线

  • bezierCurveTo:三次贝塞尔曲线,2个控制点,适合 S 形和复杂曲线

  • 控制点:不在曲线上,决定曲线弯曲方向和程度

  • 平滑连接:确保连接点处的控制点共线

  • 中点法:使用二次贝塞尔曲线的中点法绘制平滑折线

  • 参数 t:0~1 的参数控制曲线上的位置,但不等于弧长等分

  • 实际应用:心形、波浪、水滴、图表曲线、花瓣等

下一教程将介绍如何在 Canvas 上绘制文本。

常见问题

如何让多段贝塞尔曲线平滑连接?

确保连接点处的切线方向一致,即前一段的第二个控制点、连接点、后一段的第一个控制点应在同一直线上。这样可以保证曲线在连接处的平滑过渡。

如何计算贝塞尔曲线上的点?

使用贝塞尔曲线的参数方程,通过参数 t(0~1)计算。二次贝塞尔曲线公式为 B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2,三次贝塞尔曲线公式为 B(t) = (1-t)³P0 + 3(1-t)²tP1 + 3(1-t)t²P2 + t³P3。

如何沿贝塞尔曲线移动物体?

使用 requestAnimationFrame 创建动画循环,在每帧中递增参数 t(如 t += 0.005),然后使用贝塞尔曲线公式计算物体在 t 位置的坐标,清除画布后重新绘制曲线和物体。

如何绘制等距的贝塞尔曲线点?

参数 t 等分不等于弧长等分。需要先细分 t 值(如 200 步),计算每个点的累积弧长,然后根据目标弧长在累积弧长数组中查找对应点。这种方法称为查表法或近似法。

二次和三次贝塞尔曲线如何选择?

简单弧线、圆角、平滑图表使用二次贝塞尔曲线,因为参数少、易于控制;S 形曲线、字体轮廓使用三次贝塞尔曲线,因为需要两个控制点来精确控制曲线形状。

标签: 贝塞尔曲线 Canvas绘图 quadraticCurveTo bezierCurveTo 控制点 曲线动画

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 多媒体:绘制弧线与圆 - 完整教程与代码示例 下一篇: 多媒体:绘制文本 - 完整教程与代码示例

poll相关推荐