【问题标题】:Draw HTML5/Javascript Canvas Path in Time及时绘制 HTML5/Javascript 画布路径
【发布时间】:2014-10-16 01:17:04
【问题描述】:

假设我有一条路径:

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。

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

【问题讨论】:

    标签: javascript canvas html5-canvas


    【解决方案1】:

    使用均匀间隔点绘制复杂路径的演示:

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

    匀速概述

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

    “匀速”因此在每单位时间内以一致的指定距离行驶。

    因此,以每 1/60 秒 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);
    

    祝你的项目好运!

    【讨论】:

    • 我在寻找解决方案时遇到了您的代码! :-) 我真的很喜欢这个想法并自己做数学,但遗憾的是这需要大量的重复。当设置 1px 的增量时,线条的轮廓变得非常硬,看起来不再自然。您的解决方案可能适用于更多问题,但我想我会使用 Jeshua 的,因为它最适合我的特殊目的。无论如何,非常感谢!
    【解决方案2】:

    我在使用 D3.js 为 SVG 弧线设置动画时遇到了类似的问题。我的解决方案借鉴了这一点。它不是最直观的,但通常用于 D3 动画。它需要仔细设置虚线偏移和线长。 CSS Tricks gives a good explanation of the technique here which I strongly recommend reading before viewing my code.

    我已经用这种技术implemented for your line here 修改了上面的 JSFiddle。请注意,即使线路自行循环,这也会起作用。

    关于行长的说明:

    此实现要求您知道行的大​​致长度,以便您可以将长度 var 设置为大于它。对于贝塞尔曲线和二次曲线,这很棘手,但仍然可以完成 (this SO question looks promising)。对于我的演示,我通过反复试验发现你的大约是 608 像素。将长度设置为 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 BTW,您可以通过在“开始!”后添加 context.globalCompositeOperation='copy' 来平滑结果评论:jsfiddle.net/ud31ypsy.
    【解决方案3】:

    这是我的解决方案,基本上在路径上方绘制一个矩形,然后每帧更新将矩形移动 1 X 位置,所以慢慢地盒子会从路径上移开,看起来就像你在画一个动画路径,

    我已将其保存在 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);
    }
    

    【讨论】:

    • 遗憾的是,这只适用于跨一个轴的稳定路径。它不适合我的目的,因为我的路径可能有一个循环,并且应该以均匀的速度绘制路径。还是谢谢你!
    猜你喜欢
    • 1970-01-01
    • 2019-04-12
    • 1970-01-01
    • 1970-01-01
    • 2015-06-26
    • 2015-10-24
    • 1970-01-01
    • 2012-03-20
    • 1970-01-01
    相关资源
    最近更新 更多