Canvas,使图像遵循贝塞尔曲线
Canvas, make an image follow a Bezier curve
我想用 canvas 图像创建半圆后的动画。起初我尝试使用 canvas arc
但我发现使用 bezier curve
.
更简单
但现在我遇到了一个问题,因为它不在圆上,所以我找不到一种方法让它根据它的位置旋转,就像手表指针一样。到目前为止,这是我的代码。
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>
这样做的正确方法是什么?
您可以使用相同的函数在曲线上的当前点之前获取另一个点,然后使用 Math.atan2
获取两点之间的角度。
然后,您需要使用 ctx.translate()
和 ctx.rotate()
来改变转换矩阵,而不是在 .drawImage()
调用中设置位置。 (动画方法开始时的 .setTransform()
调用会重置每一帧的矩阵。)
我这里还加了个“洋葱皮”效果,这样动起来效果更好看。
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;">
无论如何都使用一个圆圈,方式更容易,但不要试图将东西放在坐标上,相反:设置坐标system到正确的偏移量和角度,这样你就可以在相同的坐标上绘制相同的东西。
我们观察到我们正在处理以 (300,600) 为原点、半径为 500、起始角(以弧度为单位)为 0.9272951769 和结束角(以弧度为单位)为 2.21429922 的圆形路径, 所以我们可以移动我们的坐标系,使 (0,0) “真的”是 (300,600),然后当我们围绕新的 (0,0) 旋转坐标系任意角度时,我们需要做的就是绘制在 (radius,0)
处的东西
因为坐标系为我们做旋转,我们不需要实际计算任何点。我们已经知道我们想要的东西在哪里:距离原点 radius
的距离。
cvs.width = 600;
cvs.height = 200;
const ctx = cvs.getContext('2d');
const statue = new Image();
statue.onload = () => animate();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
const sWidth = sHeight = 80;
// circle values matching your path:
const origin = {x: 300, y: 600 };
const radius = 500;
const start = 0.9272951769;
const end = 2.21429922;
// animation values
let step = 0;
const totalSteps = 120;
const stepSize = (end - start)/totalSteps;
// And our drawing function
function animate() {
ctx.resetTransform();
ctx.clearRect(0, 0, cvs.width, cvs.height);
if (step === totalSteps) step = 0;
const angle = start + step++ * stepSize;
// first, change the coordinate system so that we don't need
// to compute *anything* to draw it in the right place:
ctx.translate(origin.x, origin.y);
ctx.rotate(-angle);
// Then we draw the debug line:
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(radius, 0);
ctx.stroke();
// And then we draw the image. However, the image is upright
// when (radius,0) lines up with the x-axis, which means it's
// actually going to look rotated compared to our line. So:
// again, we update the coordinate system to do the work for us.
// we update it so that (radius,0) becomes (0,0), we then rotate
// it a quarter turn, and then we draw our image at (0,0).
ctx.translate(radius, 0);
ctx.rotate(Math.PI/2);
// of course, (0,0) is the image's top-left corner, so if we
// want to center it, we can do one more translation:
ctx.translate(-sWidth/2, -sHeight/2);
ctx.drawImage(statue, 0, 0, sWidth, sHeight);
// and move on to the next frame
requestAnimationFrame(animate);
}
<canvas id="cvs"></canvas>
这样,我们就不会编写 任何 代码来计算“我们需要在哪里绘制东西,以什么方向”,而是我们只使用固定点,并且让 canvas2d 上下文处理所有 translations/rotations.
还有一个鲜为人知的事实:所有浏览器都使具有 id
属性的 HTML 元素可供 JS 使用该 id 作为其变量名。所以在这种情况下我们有一个 <canvas id="cvs">
,这意味着在 JS 端有一个名为 cvs
的变量指向我们的 canvas 元素。
我想用 canvas 图像创建半圆后的动画。起初我尝试使用 canvas arc
但我发现使用 bezier curve
.
但现在我遇到了一个问题,因为它不在圆上,所以我找不到一种方法让它根据它的位置旋转,就像手表指针一样。到目前为止,这是我的代码。
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>
这样做的正确方法是什么?
您可以使用相同的函数在曲线上的当前点之前获取另一个点,然后使用 Math.atan2
获取两点之间的角度。
然后,您需要使用 ctx.translate()
和 ctx.rotate()
来改变转换矩阵,而不是在 .drawImage()
调用中设置位置。 (动画方法开始时的 .setTransform()
调用会重置每一帧的矩阵。)
我这里还加了个“洋葱皮”效果,这样动起来效果更好看。
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;">
无论如何都使用一个圆圈,方式更容易,但不要试图将东西放在坐标上,相反:设置坐标system到正确的偏移量和角度,这样你就可以在相同的坐标上绘制相同的东西。
我们观察到我们正在处理以 (300,600) 为原点、半径为 500、起始角(以弧度为单位)为 0.9272951769 和结束角(以弧度为单位)为 2.21429922 的圆形路径, 所以我们可以移动我们的坐标系,使 (0,0) “真的”是 (300,600),然后当我们围绕新的 (0,0) 旋转坐标系任意角度时,我们需要做的就是绘制在 (radius,0)
处的东西因为坐标系为我们做旋转,我们不需要实际计算任何点。我们已经知道我们想要的东西在哪里:距离原点 radius
的距离。
cvs.width = 600;
cvs.height = 200;
const ctx = cvs.getContext('2d');
const statue = new Image();
statue.onload = () => animate();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
const sWidth = sHeight = 80;
// circle values matching your path:
const origin = {x: 300, y: 600 };
const radius = 500;
const start = 0.9272951769;
const end = 2.21429922;
// animation values
let step = 0;
const totalSteps = 120;
const stepSize = (end - start)/totalSteps;
// And our drawing function
function animate() {
ctx.resetTransform();
ctx.clearRect(0, 0, cvs.width, cvs.height);
if (step === totalSteps) step = 0;
const angle = start + step++ * stepSize;
// first, change the coordinate system so that we don't need
// to compute *anything* to draw it in the right place:
ctx.translate(origin.x, origin.y);
ctx.rotate(-angle);
// Then we draw the debug line:
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(radius, 0);
ctx.stroke();
// And then we draw the image. However, the image is upright
// when (radius,0) lines up with the x-axis, which means it's
// actually going to look rotated compared to our line. So:
// again, we update the coordinate system to do the work for us.
// we update it so that (radius,0) becomes (0,0), we then rotate
// it a quarter turn, and then we draw our image at (0,0).
ctx.translate(radius, 0);
ctx.rotate(Math.PI/2);
// of course, (0,0) is the image's top-left corner, so if we
// want to center it, we can do one more translation:
ctx.translate(-sWidth/2, -sHeight/2);
ctx.drawImage(statue, 0, 0, sWidth, sHeight);
// and move on to the next frame
requestAnimationFrame(animate);
}
<canvas id="cvs"></canvas>
这样,我们就不会编写 任何 代码来计算“我们需要在哪里绘制东西,以什么方向”,而是我们只使用固定点,并且让 canvas2d 上下文处理所有 translations/rotations.
还有一个鲜为人知的事实:所有浏览器都使具有 id
属性的 HTML 元素可供 JS 使用该 id 作为其变量名。所以在这种情况下我们有一个 <canvas id="cvs">
,这意味着在 JS 端有一个名为 cvs
的变量指向我们的 canvas 元素。