如何用 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 点左右的每个点做 moveTo
和 lineTo
。
但这有三个问题:
- 计算量大
- 如何确定要计算多少分?
- 不计算几千点不还是锯齿状的吗?
有没有更好的方法?
您可以将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>
假设我有一条由以下代码生成的贝塞尔曲线:
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 点左右的每个点做 moveTo
和 lineTo
。
但这有三个问题:
- 计算量大
- 如何确定要计算多少分?
- 不计算几千点不还是锯齿状的吗?
有没有更好的方法?
您可以将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>