如何用 canvas 绘制贝塞尔曲线的一部分?

How to draw a fraction of a bezier curve with canvas?

假设我有一条由以下代码生成的贝塞尔曲线:

const ctx = document.querySelector('canvas').getContext('2d');

ctx.beginPath();
ctx.moveTo(50, 20);
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();
<canvas></canvas>

有没有办法只绘制最后 90% 的部分?

对于我的应用程序,我想“消耗”曲线,并创建一个动画,其中一个圆沿着直线路径移动,沿途消耗曲线。

我唯一能想到的就是不使用 quadraticCurveTo 函数绘制曲线,而是通过以下函数手动计算大量点:

t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;

然后对 300 点左右的每个点做 moveTolineTo

但这有三个问题:

  1. 计算量大
  2. 如何确定要计算多少分?
  3. 不计算几千点不还是锯齿状的吗?

有没有更好的方法?

您可以将ctx.setLineDash([])ctx.lineDashOffset结合使用,这是模拟路径局部绘制的常用方法。

const ctx = document.querySelector('canvas').getContext('2d');

animate();

function animate(){
    let i = 0;
    
    drawCurve(i);
    
    function drawCurve(start){
        ctx.clearRect(0,0,300,150); // initial width and height of canvas

        const line_len = 204; // to let the last part of curve stay

        ctx.setLineDash([1000]); // bigger than curve length    
        ctx.lineDashOffset = -start; // changing parameter
        
        ctx.beginPath();
        ctx.moveTo(50, 20);
        ctx.quadraticCurveTo(230, 30, 50, 100);
        ctx.stroke();
        
        const anim_id = requestAnimationFrame(() => drawCurve(++start));
        if(start > line_len) cancelAnimationFrame(anim_id);
    }
}
<canvas></canvas>

在 canvas 中获取路径的长度可能很棘手。所以我更喜欢用hidden SVG来计算。它的 <path> 具有空的 d 属性。所以我可以将我们的路径字符串分配给它和它的 getTotalLength()。现在我们有了您的路径长度,我们可以使用这些数据来正确定义 setLineDash 数组,因此 path_len(停止位置)。

我们也可以使用path.getPointAtLength()方法获取当前路径的起始位置。

animate();

function animate(){
    
    const path = document.querySelector('svg > path');
    const path_string = 'M 50 20 Q 230 30 50 100';
    path.setAttribute('d', path_string);

    const path_len = path.getTotalLength();

    const ctx = document.querySelector('canvas').getContext('2d');
    
    drawCurve(0);
    
    function drawCurve(start){
        ctx.clearRect(0,0,300,150); // initial width and height of canvas
        
        ctx.save();
        ctx.setLineDash([path_len + 1]); // bigger than curve length    
        ctx.lineDashOffset = -start; // changing parameter
        
        ctx.stroke(new Path2D(path_string));
        ctx.restore();
        
        const cur_pos = path.getPointAtLength(start - 7); // current position - (radius + 2)

        ctx.beginPath();
        ctx.arc(cur_pos.x, cur_pos.y, 5, 0, 2*Math.PI, false); // radius = 5 (should be a constant)
        ctx.fill();
        
        const anim_id = requestAnimationFrame(() => drawCurve(++start));
        if(start > path_len) cancelAnimationFrame(anim_id);
    }
}
<svg style="display:none">
    <path d=""></path>
</svg>
<canvas></canvas>