在时间轴上绘制HTML5/Javascript画布路径

3

假设我有一个路径:

var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 20);
context.lineTo(200, 160);
context.quadraticCurveTo(230, 200, 250, 120);
context.bezierCurveTo(290, -40, 300, 200, 400, 150);
context.lineTo(500, 90);
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();

这将一次性打印路径:

渲染路径

如何将路径分成给定长度的子路径?例如:context.splitCurrentPathIntoSubPath(0, 0.75) 应该只返回前3/4的路径。

我想用这个来实现动画。如果有更简单的方法,也欢迎。

3个回答

3

一个演示如何使用均匀分布的点绘制复杂路径:

http://jsfiddle.net/m1erickson/2fodu9pa/

均速概述

“速度”定义为单位时间内的距离。

“均速”因此是以每个单位时间行驶一定距离。

因此,以每秒2像素的速度沿着路径移动将是以均匀速度移动的示例。

要行进2像素,您必须计算沿路径上距离上一个点2像素的点。

在均匀速度下逐步绘制包含线条和曲线的路径需要进行数百个小计算。

以下是如何确定沿路径均匀间隔排列的点数组的方法:

  • 将路径分成其段:线、二次曲线、贝塞尔曲线、线。

  • 使用定义每个段的数学公式(参见下面的公式)计算每个段的许多(300+)点,并将这些点放入数组中。

  • 依次沿每个点行走并计算两点之间的距离(参见下面的公式)。

  • 保持行驶过的累积距离总数。

  • 当当前已行驶的距离达到指定长度时,将该点保存在第二个数组中。

然后,为逐步动画路径,您可以创建一个动画循环,将一条线绘制到第二个数组中的每个下一个点。

注意:如果保持指定距离足够小(例如1-2像素),则所绘制的线条在必要时会呈现曲线。

以下是支持此方法的公式:

计算直线上的点:

// T is an interval between 0.00 and 1.00
// To divide a Line into 300 parts you would call the function 300 times 
// with T increasing 1.00/300 each time

function getLineXYatPercent(startPt,endPt,T) {
    var dx = endPt.x-startPt.x;
    var dy = endPt.y-startPt.y;
    var X = startPt.x + dx*T;
    var Y = startPt.y + dy*T;
    return( {x:X,y:Y} );
}

计算二次曲线上的点:

// T is an interval between 0.00 and 1.00
// To divide a Quadratic Curve into 300 parts you would call the function 300 times 
// with T increasing 1.00/300 each time

function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
    var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; 
    var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; 
    return( {x:x,y:y} );
}

计算贝塞尔曲线上的点:

// T is an interval between 0.00 and 1.00
// To divide a BezierCurve into 300 parts you would call the function 300 times 
// with T increasing 1.00/300 each time

function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
    var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
    var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
    return({x:x,y:y});
}

// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T
    + (3 * b + T * (-6 * b + b * 3 * T)) * T
    + (c * 3 - c * 3 * T) * t2
    + d * t3;
}

两点之间的距离:

var dx=point2.x-point1.x;
var dy=point2.y-point1.y;
var distance=Math.sqrt(dx*dx+dy*dy);

祝你的项目好运!


1
我在寻找解决方案时遇到了你的代码! :-) 我真的很喜欢这个想法并且自己计算,但不幸的是这需要太多的重复。当设置1像素的增量时,线条的轮廓变得非常硬,看起来不再自然。你的解决方案可能适用于更多问题,但我认为我会使用Jeshua的,因为它最适合我的特殊目的。无论如何,非常感谢! - YMMD

2

当我使用D3.js动画SVG弧线时,遇到了类似的问题。我的解决方案借鉴了那个问题的解决方法。它可能不是最直观的,但在D3动画中经常使用。这需要仔细设置虚线偏移量和线条长度。CSS Tricks在这里给出了该技术的很好的解释,我强烈建议在查看我的代码之前阅读该文章

我已经使用此技术修改了上面的JSFiddle,实现了你的线条的效果。请注意,即使线条回到自身,这也可以正常工作。

关于线条长度的说明:

这种实现需要您知道线条的大致长度,以便您可以将长度变量设置为大于它。对于贝塞尔曲线和二次曲线,这是棘手的,但仍然可以完成(这个SO问题看起来很有前途)。对于我的演示,我使用试错法找到了你的长度约为608px。将长度设置为10000可能会确保您的线条始终正确绘制,但代价是每毫秒调用大量不必要的间隔回调。底线是:如果您关心性能,请找出贝塞尔公式的内容;如果您不关心,请将该变量设置得很高。

代码:

HTML

<body>
    <canvas id="canvas" width="500" height="500">
        webgl couldn't be started
    </canvas>
</body>

JavaScript

canvasHolder = document.getElementById( 'canvas' );
context = canvasHolder.getContext('2d');

context.fillStyle = 'white';
var w = canvasHolder.width, h = canvasHolder.height;
context.fillRect( 0, 0, w, h);

//set the direction the line draws in
//1->ltr | -1->rtl
var dir = -1;
//IMPORTANT: this must be set to greater than the length
//of the line
var length = 608;
//the speed of the line draw
var speed = 1;

var progress = 0;
var lineInterval;

//Go!
context.globalCompositeOperation='copy';
drawLine();

function drawLine() {
    //this clears itself once the line is drawn
    lineInterval = setInterval(updateLine, 1);
}

function updateLine() {
    //define the line
    defineLine();

    if(progress<length)
    {
      progress+=speed;
      moveDash(progress, dir);
    } else {
      clearInterval(lineInterval);
    }

}

function defineLine() {
    context.beginPath();
    context.moveTo(100, 20);
    context.lineTo(200, 160);
    context.quadraticCurveTo(230, 200, 250, 120);
    context.bezierCurveTo(290, -40, 300, 200, 400, 150);
    context.lineTo(500, 90);
    context.lineWidth = 5;
    context.strokeStyle = 'blue';
}

function moveDash(frac, dir) {
    //default direction right->left
    var dir = dir || -1 
    context.setLineDash([length]);
    context.lineDashOffset = dir*(frac+length);
    context.stroke();
}

@Jeshua 顺便说一句,你可以在“Go!”注释后添加 context.globalCompositeOperation='copy' 来平滑你的结果:http://jsfiddle.net/ud31ypsy/. - markE

1
这是我的解决方案,基本上在路径之上绘制一个矩形,然后每帧更新时将矩形沿着X轴移动1个位置,这样慢慢地将矩形移到路径之外,并看起来像是正在绘制动画路径。
我已经在jsfiddle上保存了它,这里是独立的代码。
window.addEventListener( "load", firstLoaded, false);

then = Date.now();
setInterval(main, 1); // Execute as fast as possible

var cube_x_position = 0;

function main()
{
    context.beginPath();
    context.moveTo(100, 20);
    context.lineTo(200, 160);
    context.quadraticCurveTo(230, 200, 250, 120);
    context.bezierCurveTo(290, -40, 300, 200, 400, 150);
    context.lineTo(500, 90);
    context.lineWidth = 5;
    context.strokeStyle = 'blue';
    context.stroke();

    context.fillRect(cube_x_position, 0, canvasHolder.width, canvasHolder.height);

    if(cube_x_position < canvasHolder.width)
    {
        cube_x_position += 1;
    }

}

function firstLoaded()
{
    canvasHolder = document.getElementById( 'canvas' );
    context = canvasHolder.getContext('2d');

    context.fillStyle = "#AAAAAA";
    context.fillRect( 0, 0, 500, 500);
}

很遗憾,这只适用于沿一个轴的稳定路径。它不适合我的目的,因为我的路径可能有一个循环,并且路径应该以均匀的速度绘制。无论如何,谢谢! - YMMD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接