I want to create an animation with canvas of an image following a semi circle. At first I've tried with a canvas arc
but I've found it simpler with a bezier curve
.
But now I am facing a problem because since it's not on a circle I can't find a way to make it rotate according to it's position, like a watch pointer. This is my code so far.
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
var statue = new Image();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
var x =
Math.pow(1 - percent, 2) * startPt.x +
2 * (1 - percent) * percent * controlPt.x +
Math.pow(percent, 2) * endPt.x;
var y =
Math.pow(1 - percent, 2) * startPt.y +
2 * (1 - percent) * percent * controlPt.y +
Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}
const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };
var percent = 0;
statue.addEventListener('load', () => {
animate();
});
function animate() {
//console.log(percent);
ctx.clearRect(0, 0, c.width, c.height);
percent > 1 ? (percent = 0) : (percent += 0.003);
var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
ctx.drawImage(
statue,
0,
0,
statue.width,
statue.height,
point.x - 50,
point.y - 50,
100,
100
);
//ctx.fillRect(point.x, point.y, 10, 10);
requestAnimationFrame(animate);
}
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="600" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
</script>
</body>
</html>
What is the proper way to do so?
You can use the same function to get another point slightly before the current one on the curve, then use Math.atan2
to get the angle between the two points.
Then, you'll need to use ctx.translate()
and ctx.rotate()
to mutate the transformation matrix instead of setting the position in the .drawImage()
call. (The .setTransform()
call at the start of the animation method resets the matrix for each frame.)
I also added an "onion skin" effect here, so the motion is better seen.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var statue = new Image();
statue.src = "https://i.ibb.co/3TvCH8n/liberty.png";
function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
var x =
Math.pow(1 - percent, 2) * startPt.x +
2 * (1 - percent) * percent * controlPt.x +
Math.pow(percent, 2) * endPt.x;
var y =
Math.pow(1 - percent, 2) * startPt.y +
2 * (1 - percent) * percent * controlPt.y +
Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}
const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };
var percent = 0;
statue.addEventListener("load", () => {
ctx.clearRect(0, 0, c.width, c.height);
animate();
});
function animate() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
// "Onion skin" effect so the last frame is slightly retained to better show the motion.
ctx.fillStyle = "rgba(255,255,255,0.1)";
ctx.fillRect(0, 0, c.width, c.height);
percent = (percent + 0.003) % 1;
var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
var lastPoint = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent - 0.003);
var angle = Math.atan2(lastPoint.y - point.y, lastPoint.x - point.x);
// Debug pointer line
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.lineTo(point.x + Math.cos(angle) * 50, point.y + Math.sin(angle) * 50);
ctx.stroke();
// Actual drawing
ctx.translate(point.x, point.y);
ctx.rotate(angle);
ctx.drawImage(statue, 0, 0, statue.width, statue.height, -50, -50, 100, 100);
requestAnimationFrame(animate);
}
<canvas id="myCanvas" width="600" height="400" style="border:1px solid #d3d3d3;">