jQuery 我想沿着特定路径制作对象的动画
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17083580/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
I want to do animation of an object along a particular path
提问by Chandni
I have to move the small rectangle on the path. The rectangle moves after a click inside the canvas.
我必须在路径上移动小矩形。在画布内单击后,矩形会移动。
I am not able to animate it as the object just jumps to the required point.
我无法为其设置动画,因为对象只是跳转到所需的点。
Please find the code on Fiddle.
请在Fiddle上找到代码。
HTML
HTML
<canvas id="myCanvas" width=578 height=200></canvas>
CSS
CSS
#myCanvas {
width:578px;
height:200px;
border:2px thin;
}
JavaScript
JavaScript
var myRectangle = {
x: 100,
y: 20,
width: 25,
height: 10,
borderWidth: 1
};
$(document).ready(function () {
$('#myCanvas').css("border", "2px solid black");
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var cntxt = canvas.getContext('2d');
drawPath(context);
drawRect(myRectangle, cntxt);
$('#myCanvas').click(function () {
function animate(myRectangle, canvas, cntxt, startTime) {
var time = (new Date()).getTime() - startTime;
var linearSpeed = 10;
var newX = Math.round(Math.sqrt((100 * 100) + (160 * 160)));
if (newX < canvas.width - myRectangle.width - myRectangle.borderWidth / 2) {
myRectangle.x = newX;
}
context.clearRect(0, 0, canvas.width, canvas.height);
drawPath(context);
drawRect(myRectangle, cntxt);
// request new frame
requestAnimFrame(function () {
animate(myRectangle, canvas, cntxt, startTime);
});
}
drawRect(myRectangle, cntxt);
myRectangle.x = 100;
myRectangle.y = 121;
setTimeout(function () {
var startTime = (new Date()).getTime();
animate(myRectangle, canvas, cntxt, startTime);
}, 1000);
});
});
$(document).keypress(function (e) {
if (e.which == 13) {
$('#myCanvas').click();
}
});
function drawRect(myRectangle, cntxt) {
cntxt.beginPath();
cntxt.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height);
cntxt.fillStyle = 'cyan';
cntxt.fill();
cntxt.strokeStyle = 'black';
cntxt.stroke();
};
function drawPath(context) {
context.beginPath();
context.moveTo(100, 20);
// line 1
context.lineTo(200, 160);
// quadratic curve
context.quadraticCurveTo(230, 200, 250, 120);
// bezier curve
context.bezierCurveTo(290, -40, 300, 200, 400, 150);
// line 2
context.lineTo(500, 90);
context.lineWidth = 5;
context.strokeStyle = 'blue';
context.stroke();
};
回答by markE
Here is how to move an object along a particular path
以下是如何沿特定路径移动对象
Animation involves movement over time. So for each “frame” of your animation you need to know the XY coordinate where to draw your moving object (rectangle).
动画涉及随时间推移的运动。因此,对于动画的每个“帧”,您需要知道绘制移动对象(矩形)的位置的 XY 坐标。
This code takes in a percent-complete (0.00 to 1.00) and returns the XY coordinate which is that percentage along the path segment. For example:
此代码采用百分比完成(0.00 到 1.00)并返回 XY 坐标,即沿路径段的百分比。例如:
- 0.00 will return the XY at the beginning of the line (or curve).
- 0.50 will return the XY at the middle of the line (or curve).
- 1.00 will return the XY at the end of the line (or curve).
- 0.00 将返回线(或曲线)开头的 XY。
- 0.50 将返回线(或曲线)中间的 XY。
- 1.00 将返回线(或曲线)末端的 XY。
Here is the code to get the XY at the specified percentage along a line:
这是沿线以指定百分比获取 XY 的代码:
// line: percent is 0-1
function getLineXYatPercent(startPt,endPt,percent) {
var dx = endPt.x-startPt.x;
var dy = endPt.y-startPt.y;
var X = startPt.x + dx*percent;
var Y = startPt.y + dy*percent;
return( {x:X,y:Y} );
}
Here is the code to get the XY at the specified percentage along a quadratic bezier curve:
这是沿二次贝塞尔曲线以指定百分比获取 XY 的代码:
// quadratic bezier: percent is 0-1
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} );
}
Here is the code to get the XY at the specified percentage along a cubic bezier curve:
这是沿三次贝塞尔曲线以指定百分比获取 XY 的代码:
// cubic bezier percent is 0-1
function getCubicBezierXYatPercent(startPt,controlPt1,controlPt2,endPt,percent){
var x=CubicN(percent,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(percent,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at percent distance
function CubicN(pct, a,b,c,d) {
var t2 = pct * pct;
var t3 = t2 * pct;
return a + (-a * 3 + pct * (3 * a - a * pct)) * pct
+ (3 * b + pct * (-6 * b + b * 3 * pct)) * pct
+ (c * 3 - c * 3 * pct) * t2
+ d * t3;
}
And here is how you put it all together to animate the various segments of your path
这是您如何将它们组合在一起以动画化路径的各个部分
// calculate the XY where the tracking will be drawn
if(pathPercent<25){
var line1percent=pathPercent/24;
xy=getLineXYatPercent({x:100,y:20},{x:200,y:160},line1percent);
}
else if(pathPercent<50){
var quadPercent=(pathPercent-25)/24
xy=getQuadraticBezierXYatPercent({x:200,y:160},{x:230,y:200},{x:250,y:120},quadPercent);
}
else if(pathPercent<75){
var cubicPercent=(pathPercent-50)/24
xy=getCubicBezierXYatPercent({x:250,y:120},{x:290,y:-40},{x:300,y:200},{x:400,y:150},cubicPercent);
}
else {
var line2percent=(pathPercent-75)/25
xy=getLineXYatPercent({x:400,y:150},{x:500,y:90},line2percent);
}
// draw the tracking rectangle
drawRect(xy);
Here is working code and a Fiddle: http://jsfiddle.net/m1erickson/LumMX/
这是工作代码和小提琴:http: //jsfiddle.net/m1erickson/LumMX/
<!doctype html>
<html lang="en">
<head>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script>
$(function() {
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// set starting values
var fps = 60;
var percent=0
var direction=1;
// start the animation
animate();
function animate() {
// set the animation position (0-100)
percent+=direction;
if(percent<0){ percent=0; direction=1; };
if(percent>100){ percent=100; direction=-1; };
draw(percent);
// request another frame
setTimeout(function() {
requestAnimationFrame(animate);
}, 1000 / fps);
}
// draw the current frame based on sliderValue
function draw(sliderValue){
// redraw path
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(100, 20);
ctx.lineTo(200, 160);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 160);
ctx.quadraticCurveTo(230, 200, 250, 120);
ctx.strokeStyle = 'green';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(250,120);
ctx.bezierCurveTo(290, -40, 300, 200, 400, 150);
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(400, 150);
ctx.lineTo(500, 90);
ctx.strokeStyle = 'gold';
ctx.stroke();
// draw the tracking rectangle
var xy;
if(sliderValue<25){
var percent=sliderValue/24;
xy=getLineXYatPercent({x:100,y:20},{x:200,y:160},percent);
}
else if(sliderValue<50){
var percent=(sliderValue-25)/24
xy=getQuadraticBezierXYatPercent({x:200,y:160},{x:230,y:200},{x:250,y:120},percent);
}
else if(sliderValue<75){
var percent=(sliderValue-50)/24
xy=getCubicBezierXYatPercent({x:250,y:120},{x:290,y:-40},{x:300,y:200},{x:400,y:150},percent);
}
else {
var percent=(sliderValue-75)/25
xy=getLineXYatPercent({x:400,y:150},{x:500,y:90},percent);
}
drawRect(xy,"red");
}
// draw tracking rect at xy
function drawRect(point,color){
ctx.fillStyle="cyan";
ctx.strokeStyle="gray";
ctx.lineWidth=3;
ctx.beginPath();
ctx.rect(point.x-13,point.y-8,25,15);
ctx.fill();
ctx.stroke();
}
// draw tracking dot at xy
function drawDot(point,color){
ctx.fillStyle=color;
ctx.strokeStyle="black";
ctx.lineWidth=3;
ctx.beginPath();
ctx.arc(point.x,point.y,8,0,Math.PI*2,false);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// line: percent is 0-1
function getLineXYatPercent(startPt,endPt,percent) {
var dx = endPt.x-startPt.x;
var dy = endPt.y-startPt.y;
var X = startPt.x + dx*percent;
var Y = startPt.y + dy*percent;
return( {x:X,y:Y} );
}
// quadratic bezier: percent is 0-1
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} );
}
// cubic bezier percent is 0-1
function getCubicBezierXYatPercent(startPt,controlPt1,controlPt2,endPt,percent){
var x=CubicN(percent,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(percent,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at percent distance
function CubicN(pct, a,b,c,d) {
var t2 = pct * pct;
var t3 = t2 * pct;
return a + (-a * 3 + pct * (3 * a - a * pct)) * pct
+ (3 * b + pct * (-6 * b + b * 3 * pct)) * pct
+ (c * 3 - c * 3 * pct) * t2
+ d * t3;
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=600 height=300></canvas>
</body>
</html>
回答by Chandni
If you're gonna use the built-in Bezier curves of the canvas, you would still need to do the math yourself.
如果您要使用画布的内置贝塞尔曲线,您仍然需要自己进行数学计算。
You can use this implementation of a cardinal splineand have all the points returned for you pre-calculated.
您可以使用基数样条的这种实现,并预先计算为您返回的所有点。
An example of usage is this little sausage-mobile moving along the slope (generated with the above cardinal spline):
一个使用示例是这个沿着斜坡移动的小香肠移动(使用上述基数样条生成):
Full demo here(cut-and-copy as you please).
完整演示在这里(随意剪切和复制)。
The main things you need is when you have the point array is to find two points you want to use for the object. This will give us the angle of the object:
当您拥有点数组时,您需要做的主要事情是找到要用于对象的两个点。这将为我们提供对象的角度:
cPoints = quantX(pointsFromCardinalSpline); //see below
//get points from array (dx = current array position)
x1 = cPoints[dx];
y1 = cPoints[dx + 1];
//get end-points from array (dlt=length, must be an even number)
x2 = cPoints[dx + dlt];
y2 = cPoints[dx + dlt + 1];
To avoid stretching in steeper slopes we recalculate the length based on angle. To get an approximate angle we use the original end-point to get an angle, then we calculate a new length of the line based on wanted length and this angle:
为了避免在更陡峭的斜坡上拉伸,我们根据角度重新计算长度。为了得到一个近似角度,我们使用原始端点来获得一个角度,然后我们根据想要的长度和这个角度计算新的线长度:
var dg = getLineAngle(x1, y1, x2, y2);
var l = ((((lineToAngle(x1, y2, dlt, dg).x - x1) / 2) |0) * 2);
x2 = cPoints[dx + l];
y2 = cPoints[dx + l + 1];
Now we can plot the "car" along the slope by subtracting it's vertical height from the y positions.
现在我们可以通过从 y 位置减去它的垂直高度来沿着斜坡绘制“汽车”。
What you will notice doing just this is that the "car" moves at variable speed. This is due to the interpolation of the cardinal spline.
您会注意到这样做的是“汽车”以可变速度移动。这是由于基数样条的插值。
We can smooth it out so the speed look more even by quantize the x axis. It will still not be perfect as in steep slopes the y-distance between to points will be greater than on a flat surface - we would really need a quadratic quantization, but for this purpose we do only the x-axis.
我们可以通过量化 x 轴来平滑它,使速度看起来更均匀。它仍然不是完美的,因为在陡峭的斜坡上,点之间的 y 距离将大于平面上的距离 - 我们确实需要二次量化,但为此目的,我们只做 x 轴。
This gives us a new array with new points for each x-position:
这为我们提供了一个新数组,每个 x 位置都有新点:
function quantX(pts) {
var min = 99999999,
max = -99999999,
x, y, i, p = pts.length,
res = [];
//find min and max of x axis
for (i = 0; i < pts.length - 1; i += 2) {
if (pts[i] > max) max = pts[i];
if (pts[i] < min) min = pts[i];
}
max = max - min;
//this will quantize non-existng points
function _getY(x) {
var t = p,
ptX1, ptX2, ptY1, ptY2, f, y;
for (; t >= 0; t -= 2) {
ptX1 = pts[t];
ptY1 = pts[t + 1];
if (x >= ptX1) {
//p = t + 2;
ptX2 = pts[t + 2];
ptY2 = pts[t + 3];
f = (ptY2 - ptY1) / (ptX2 - ptX1);
y = (ptX1 - x) * f;
return ptY1 - y;
}
}
}
//generate new array per-pixel on the x-axis
//note: will not work if curve suddenly goes backwards
for (i = 0; i < max; i++) {
res.push(i);
res.push(_getY(i));
}
return res;
}
The other two functions we need is the one calculating the angle for a line, and the one calculating end-points based on angle and length:
我们需要的另外两个函数是计算直线角度的函数,以及基于角度和长度计算端点的函数:
function getLineAngle(x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1,
th = Math.atan2(dy, dx);
return th * 180 / Math.PI;
}
function lineToAngle(x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
return {x: x2, y: y2};
}