Michael Horace
Michael Horace

Reputation: 1

Animating drawing arcTo lines on canvas

I am trying to implement an animation of drawing an arcTo line on Canvas. For a straight line for example, the animation would be as follows

c = canvas.getContext("2d");

width = window.innerWidth;
height = window.innerHeight;
complete = false
var percent = 1

function drawEdge(x1, y1, x2, y2, color){
 c.beginPath();
 c.lineWidth = 10;
 c.strokeStyle = color;
 c.moveTo(x1, y1);
 c.lineTo(x2, y2);
 c.stroke();
 c.closePath();
}

function getPosition(x1, y1, x2, y2, percentageBetweenPoints){
 let xPosition = x1 + (x2 - x1) * (percentageBetweenPoints / 100);
 let yPosition = y1 + (y2 - y1) * (percentageBetweenPoints / 100);

 const position = {
     x: xPosition,
     y: yPosition,
 }
 return position
}

function drawLine(){
 if (!complete){
     requestAnimationFrame(drawLine);
 }

 if (percent >= 100){
     complete = true;
     percent = 100;
 } else{
     percent = percent + 1;
 }

 position = getPosition(300,300,1000,300,percent);
 c.clearRect(0, 0 , width, height);
 drawEdge(300,300,position.x,position.y, "black");

}

drawLine()

This creates an animation of a line being drawn across the screen. However, I am having trouble doing the same thing for arcTo lines. Is there any way to implement this?

Upvotes: 0

Views: 289

Answers (2)

Blindman67
Blindman67

Reputation: 54089

To Hack or not to Hack?

There are two ways to do this

  1. Calculate the start, end, and length of each line segment, the start, end angle, direction (CW or CCW), and center of each arc segment. Basically repeating all the maths and logic (around 50 lines of code) that makes arcTo such a useful render function.

    You can get details on how to approach the full solution from html5 canvas triangle with rounded corners

  2. Use ctx.lineDash with a long dash and a long space. Move the dash over time with ctx.lineDashOffset giving the appearance of a line growing in length (see demo). The dash offset value is reversed, starting at max length and ending when zero.

    NOTE there is one problem with this method. You don't know the length of the line, and thus you don`t know how long it will take for the line to be completed. You can make an estimation. To know the length of the line you must do all the calculations (well there abouts)

The Hack

As the second method is the easiest to implement and covers most needs I will demo that method.

Not much to say about it, it animates a path created by ctx.arcTo Side benefit is it will animated any path rendered using ctx.stroke

requestAnimationFrame(mainLoop);
// Line is defined in unit space. 
// Origin is at center of canvas, -1,-1 top left, 1, 1 bottom right
// Unit box is square and will be scaled to fit the canvas size.
// Note I did not use ctx.setTransform to better highlight what is scaled and what is not.
const ctx = canvas.getContext("2d");
var w, h, w2, h2;          // canvas size and half size
var linePos;               // current dash offset
var scale;                 // canvas scale
const LINE_WIDTH = 0.05;      // in units
const LINE_STYLE = "#000"; // black
const LINE_SPEED = 1;      // in units per second
const MAX_LINE_LENGTH = 9; // in units approx
const RADIUS = 0.08;       //Arc radius in units
const SHAPE = [[0.4, 0.2], [0.8, 0.2], [0.5, 0.5], [0.95, 0.95], [0.0, 0.5], [-0.95, 0.95], [-0.5, 0.5], [-0.8, 0.2], [-0.2, 0.2],  [-0.2, -0.2], [-0.8, -0.2], [-0.5, -0.5], [-0.95, -0.95], [0.0, -0.5], [0.95,-0.95], [0.5, -0.5], [0.8, -0.2], [0.2, -0.2], [0.2, 0.2], [0.6, 0.2], [0.8, 0.2]];

function sizeCanvas() {
    w2 = (w = canvas.width = innerWidth) / 2;
    h2 = (h = canvas.height = innerHeight) / 2;
    scale = Math.min(w2, h2);
    resetLine();
}

function addToPath(shape) {
   var p1, p2;
   for (p2 of shape) {
       !p2.length ? 
           ctx.closePath() :
           (p1 ? ctx.arcTo(p1[0] * scale + w2, p1[1] * scale + h2, p2[0] * scale + w2, p2[1] * scale + h2, RADIUS * scale) : 
                ctx.lineTo(p2[0] * scale + w2, p2[1] * scale + h2)
            );
       p1 = p2;
   }
}

function resetLine() {
    ctx.setLineDash([MAX_LINE_LENGTH * scale, MAX_LINE_LENGTH * scale]);
    linePos = MAX_LINE_LENGTH * scale;
    ctx.lineWidth = LINE_WIDTH  * scale;
    ctx.lineJoin = ctx.lineCap = "round";
}

function mainLoop() {
    if (w !== innerWidth || h !== innerHeight) { sizeCanvas() }
    else { ctx.clearRect(0, 0, w, h) }
    ctx.beginPath();
    addToPath(SHAPE);  
    ctx.lineDashOffset = (linePos -= LINE_SPEED * scale * (1 / 60));
    ctx.stroke();
    if (linePos <= 0) { resetLine() }
    requestAnimationFrame(mainLoop);
}
    
    
body {
   padding: 0px,
   margin: 0px;
}
canvas {
   position: absolute;
   top: 0px;
   left: 0px;
}
<canvas id="canvas"></canvas>

Upvotes: 0

Stranger in the Q
Stranger in the Q

Reputation: 3898

You are looking for something like this?

let ctx = canvas.getContext('2d');
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = 'bold 18px Arial';

requestAnimationFrame(draw);

function draw(t) {
  t = t % 5e3 / 5e3;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(canvas.width/2, canvas.height/2, 50, 0, t * 2 * Math.PI);
  ctx.stroke();
  ctx.fillText((t*100).toFixed(0), canvas.width/2, canvas.height/2);
  requestAnimationFrame(draw);
}
<canvas id=canvas></canvas>

Upvotes: 2

Related Questions