iamai
iamai

Reputation: 523

Move an object through set of coordinates on HTML5 Canvas

I want to move a object (circle in this case) through array of coordinates (for example: {(300,400), (200,300), (300,200),(400,400)})on HTML5 Canvas. I could move the object to one coordinate as follows. The following code draws a circle at (100,100) and moves it to (300,400). I am stuck when trying to extend this so that circle moves through set of coordinates one after the other.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

//circle object

let circle ={
x:100, 
y:100,
radius:10,
dx:1,
dy:1,
color:'blue'
}

//function to draw above circle on canvas 
function drawCircle(){

        ctx.beginPath();
        ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
        ctx.fillStyle=circle.color;
        ctx.fill();
        ctx.closePath();
}

//Moving to a target coordinate (targetX,targetY)

function goTo(targetX,targetY){

        if(Math.abs(circel.x-targetX)<circle.dx && Math.abs(circel.y-targetY)<circle.dy){

            circle.dx=0;
            circle.dy=0;
            circel.x = targetX;
            circle.y = targetY;
        }

        else{

        const opp = targetY - circle.y;
        const adj = targetX - circle.x;

        const angle = Math.atan2(opp,adj)

        circel.x += Math.cos(angle)*circle.dx
        circle.y += Math.sin(angle)*circle.dy

        }

}


function update(){
         ctx.clearRect(0,0,canvas.width,canvas.height);
         
         drawCircle()
         goTo(300,400)
         requestAnimationFrame(update);

}

update()

Upvotes: 2

Views: 1324

Answers (2)

Blindman67
Blindman67

Reputation: 54128

Random access key frames

For the best control of animations you need to create way points (key frames) that can be accessed randomly by time. This means you can get any position in the animation just by setting the time.

You can then play and pause, set speed, reverse and seek to any position in the animation.

Example

The example below uses a set of points and adds data required to quickly locate the key frames at the requested time and interpolate the position.

The blue dot will move at a constant speed over the path in a time set by pathTime in this case 4 seconds.

The red dot's position is set by the slider. This is to illustrate the random access of the animation position.

const ctx = canvas.getContext('2d');
const pathTime = 4; // Total time to travel path from start to end in seconds
var startTime, animTime = 0, paused = false;
requestAnimationFrame(update);
const P2 = (x, y) => ({x, y, dx: 0,dy: 0,dist: 0, start: 0, end: 0});
const pathCoords = [
  P2(20, 20), P2(100, 50),P2(180, 20), P2(150, 100), P2(180, 180),   
  P2(100, 150), P2(20, 180), P2(50, 100), P2(20, 20),
];
createAnimationPath(pathCoords);
const circle ={
    draw(rad = 10, color = "blue") {
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(this.x, this.y, rad, 0, Math.PI * 2);
        ctx.fill();
    }
};
function createAnimationPath(points) { // Set up path for easy random position lookup
    const segment = (prev, next) => {
        [prev.dx, prev.dy] = [next.x - prev.x, next.y - prev.y];
        prev.dist = Math.hypot(prev.dx, prev.dy);
        next.end = next.start = prev.end = prev.start + prev.dist;
    }
    var i = 1;
    while (i < points.length) { segment(points[i - 1], points[i++]) }
}
function getPos(path, pos, res = {}) {
    pos = (pos % 1) * path[path.length - 1].end;       // loop & scale to total length 
    const pathSeg = path.find(p => pos >= p.start && pos <= p.end);
    const unit = (pos - pathSeg.start) / pathSeg.dist; // unit distance on segment
    res.x = pathSeg.x + pathSeg.dx * unit;             // x, y position on segment
    res.y = pathSeg.y + pathSeg.dy * unit;
    return res;
}
function update(time){
    // startTime ??= time;         // Throws syntax on iOS
    startTime = startTime ?? time; // Fix for above
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (paused) { startTime = time - animTime }
    else { animTime = time - startTime }
    getPos(pathCoords, (animTime / 1000) / pathTime, circle).draw(); 
    getPos(pathCoords, timeSlide.value, circle).draw(5, "red"); 
    requestAnimationFrame(update);
}
pause.addEventListener("click", ()=> { paused = true; pause.classList.add("pause") });
play.addEventListener("click", ()=> { paused = false; pause.classList.remove("pause") });
rewind.addEventListener("click", ()=> { startTime = undefined; animTime = 0 });
div {
    position:absolute;
    top: 5px;
    left: 20px;
}
#timeSlide {width: 360px}
.pause {color:blue}
button {height: 30px}
<div><input id="timeSlide" type="range" min="0" max="1" step="0.001" value="0" width= "200"><button id="rewind">Start</button><button id="pause">Pause</button><button id="play">Play</button></div>
<canvas id="canvas" width="200" height="200"></canvas>

Upvotes: 2

Darth
Darth

Reputation: 1650

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// array of path coords
const pathCoords = [
  [200,100], 
  [300, 150], 
  [200,190],
  [400,100],
  [50,10],
  [150,10], 
  [0, 50], 
  [500,90],
  [20,190],
  [10,180],
];

// current point
let currentTarget = pathCoords.shift();

//circle object
const circle ={
  x:10, 
  y:10,
  radius:10,
  dx:2,
  dy:2,
  color:'blue'
}

//function to draw above circle on canvas 
function drawCircle(){
  ctx.beginPath();
  ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2);
  ctx.fillStyle=circle.color;
  ctx.fill();
  ctx.closePath();
}

//Moving to a target coordinate (targetX,targetY)
function goTo(targetX, targetY){
  if(Math.abs(circle.x-targetX)<circle.dx && Math.abs(circle.y-targetY)<circle.dy){
    // dont stop...
    //circle.dx = 0;
    //circle.dy = 0;
    circle.x = targetX;
    circle.y = targetY;
    
    // go to next point
    if (pathCoords.length) {
      currentTarget = pathCoords.shift();
    } else {
      console.log('Path end');
    }
  } else {
    const opp = targetY - circle.y;
    const adj = targetX - circle.x;

    const angle = Math.atan2(opp,adj)
    circle.x += Math.cos(angle)*circle.dx
    circle.y += Math.sin(angle)*circle.dy
  }
}

function update(){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  drawCircle();
  goTo(...currentTarget);
  requestAnimationFrame(update);
}

update();
<canvas id=canvas width = 500 height = 200></canvas>

Upvotes: 1

Related Questions