Draw HTML5 / Jvascript Canvas Path in time

Let's say I have a path:

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();

      

This prints the path right away:

Rendered path

How can I split the path into subfolders of a given length? For example: context.splitCurrentPathIntoSubPath(0, 0.75)

should only return the first 3/4 of the path.

I would like to use this to implement animation. If there is an easier method, it is also welcome.

+3


source to share


3 answers


I faced a similar issue when animating SVG arcs using D3.js. My solution is borrowing this. It's not the most intuitive, but is commonly used in D3 animations. It requires careful tuning of the dash offset and line length. CSS Tricks gives a good explanation of the technique, which I highly recommend reading before looking at my code .

I modified the above JSFiddle with this technique implemented for your line here . Note that this will work even if the line goes back on its own.

A note on line length:

This implementation requires you to know the approximate length of your string so that you can set var to be longer than it. For bezier and quadratic curves, this is tricky but can still be done ( this SO question looks promising ). For my demo, I used trial and error to find out you have around 608px. By setting the length to 10000, probably make sure your lines are always drawn correctly, but at the cost of many unnecessary interval callbacks every millisecond. The bottom line is: If you care about performance, figure out the bezier formula; if you don't, set this variable high.

Code:



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();
}

      

+2


source


This is my solution, basically draw a rectangle above your path, then each frame update moves the rectangle 1 X position so that slowly the box will move and move away from the path and it will look like you are drawing an animated path.

I saved it on a jsfiddle for you :) and here is a separate code



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);
}

      

+1


source


Demonstration of a complex path using evenly spaced points:

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

Unified Speed ​​Overview

"Speed" is defined as the distance per unit of time.

A "speed uniform" therefore moves an agreed predetermined distance per unit of time.

So moving along your 2 pixel path in 1 / 60th of a second would be an example of moving at a uniform speed.

To travel 2 pixels, you must compute a point along your path that is 2 pixels from your last point.

Drawing a path that contains lines and curves step by step at a uniform speed requires hundreds of little calculations.

Here's how to define an array of points evenly spaced along your path:

  • Divide your path into your segments: line, quadratic curve, bezier curve, line.

  • Calculate many (300+) points along each segment using the mathematical formula defining each segment (see formulas below) and place those points in the array.

  • Walk through each point in sequence and calculate the distance between the points (see the formula below).

  • Keep the total accumulated distance traveled by points.

  • When the currently moved point reaches the specified length, store that point in a second array.

Then, to animate the path gradually, you can create an animation loop that draws a line for each additional point in the second array.

Note. If you keep the given distance small enough (for example 1-2 pixels), then the drawn lines will appear curved if necessary.

Here is the formula that supports this method:

Calculate points along a line:

// 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} );
}

      

Calculate points by quadratic curve:

// 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} );
}

      

Calculate Bezier Points:

// 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;
}

      

Distance between two points:

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

      

Good luck with your project!

+1


source







All Articles