将放松融入基于时间的动作中

时间:2019-03-11 08:39:24

标签: javascript animation math time html5-canvas

我的目标是在项目中将缓动纳入基于时间的动作中,并且可能需要一些帮助。

目前我正在使用每秒x像素的简单公式。

...
speed: 100,
now: undefined,
delta: undefined,
then: undefined,
setDelta: function() {
    this.now = Date.now();
    this.delta = (this.now - this.then) / 1000;
    this.then = this.now;
},
...

var slice = this.speed * this.delta;
this.x += Math.cos(rad) * slice;
this.y += Math.sin(rad) * slice;

这样做,我的对象以每秒100像素的速度运动。 但是,动画非常无聊,因此可以通过使其慢速开始,加速到一半距离然后再次开始慢速直到到达目的地来使它变得更有趣。

我找到了JavaScript click的缓动函数列表。

我认为看似准确的是一些平滑的正弦,就像这样:

easeInOutSin: function (t) {
    return (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2;
}

但是问题是我无法弄清楚如何将该公式“连接”到我的代码(如上所示)。

在链接上,这些家伙声称t是从01的参数,我认为可能需要改变的是speed。也许有人可以帮忙。

这是一个用于实验的演示代码段:

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
	rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x);

let cvs = document.createElement('canvas'),
	ctx = cvs.getContext('2d'),
	w = cvs.width = 700,
	h = cvs.height = 200,
	cx = w / 2,
	cy = h / 2;

let obj = {
	x: 100,
	y: cy,
	speed: 100,
	dx: 600,
	dy: cy,
	run: function() {
		if(!this.moving) { return; }
		let d = distance(this);
		if(d < 1) {
			this.end();
		}
		this.setDelta();
		var slice = this.speed * this.delta;
		let rad = rftv(this);
		this.x += Math.cos(rad) * slice;
		this.y += Math.sin(rad) * slice;
	},
	now: undefined,
	delta: undefined,
	then: undefined,
	setDelta: function() {
		this.now = Date.now();
		this.delta = (this.now - this.then) / 1000;
		this.then = this.now;
	},
	moving: false,
	start: function() {
		this._started_ = Date.now();
		this.then = Date.now();
		this.moving = true;
	},
	end: function() {
		this.moving = false;
		console.log( Date.now() - this._started_, 'should be close to 5000' );
	}
};

let render = () => {
	ctx.fillStyle = '#ccc';
	ctx.fillRect(0, 0, w, h);

	ctx.beginPath();
	ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
	ctx.closePath();
	ctx.strokeStyle = 'red';
	ctx.stroke();
	obj.run();

	requestAnimationFrame(render);
};

document.body.appendChild(cvs);
render();

obj.start();

1 个答案:

答案 0 :(得分:1)

您选择的缓动功能只是时间的函数。这意味着它会根据时间(也从0到1)返回0到1的比率。这意味着您必须计算经过的时间与所需的总动画时间的比率。然后,要计算位置,您需要将返回的比率应用于您要移动的总距离(this.dx - this.startX),并将其添加到开始位置。

请注意,在以下示例中,我忽略了radthis.then的计算,我并没有真正理解rad的含义,并且正如您所看到的,缓动必须起作用总距离和动画总时间因此,也没有速度的概念,或者您必须将其应用于总距离/动画时间。

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
	rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x),
  easeInOutSin = function (t) {
    return (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2;
  };

let cvs = document.createElement('canvas'),
	ctx = cvs.getContext('2d'),
	w = cvs.width = 700,
	h = cvs.height = 200,
	cx = w / 2,
	cy = h / 2;

let obj = {
	x: 100,
  startX: 100,
	y: cy,
	//speed: 100,
	dx: 600,
	dy: cy,
	run: function() {
		if(!this.moving) { return; }
		let d = distance(this);
		if(d < 1) {
			this.end();
		}
		this.setDelta();
		/*var slice = this.speed * this.delta;
		let rad = rftv(this);
		this.x += Math.cos(rad) * slice;*/
		this.x = this.startX + (this.delta * (this.dx - this.startX));
		//this.y += Math.sin(rad) * slice;
	},
	now: undefined,
	delta: undefined,
	//then: undefined,
	setDelta: function() {
		this.now = Date.now();
		this.delta = easeInOutSin( (this.now - this._started_) / 5000 ); //(this.now - this.then) / 1000;
		//this.then = this.now;
	},
	moving: false,
	start: function() {
		this._started_ = Date.now();
		this.then = Date.now();
		this.moving = true;
	},
	end: function() {
		this.moving = false;
		console.log( Date.now() - this._started_, 'should be close to 5000' );
	}
};

let render = () => {
	ctx.fillStyle = '#ccc';
	ctx.fillRect(0, 0, w, h);

	ctx.beginPath();
	ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
	ctx.closePath();
	ctx.strokeStyle = 'red';
	ctx.stroke();
	obj.run();

	if(obj.moving){ requestAnimationFrame(render); }
};

document.body.appendChild(cvs);

obj.start();

render();

这是第二个示例,它具有一个更高级的缓动功能,该功能从this answer改编而成,它具有4个参数:经过时间,开始和结束值以及总动画时间。返回值直接是您的x头寸。 编辑:固定的应用参数,应为0和总距离,然后将其添加到起始位置。

let distance = (p) => Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy)),
	rftv = (p) => Math.atan2(p.dy - p.y, p.dx - p.x),
    easeInOutSine = (t, startVal, endVal, totalTime) => (-endVal/2 * (Math.cos(Math.PI*t/totalTime) - 1) + startVal);

let cvs = document.createElement('canvas'),
	ctx = cvs.getContext('2d'),
	w = cvs.width = 700,
	h = cvs.height = 200,
	cx = w / 2,
	cy = h / 2;

let obj = {
	x: 100,
    startX: 100,
	y: cy,
	//speed: 100,
	dx: 600,
	dy: cy,
	run: function() {
		if(!this.moving) { return; }
		let d = distance(this);
		if(d < 1) {
			this.end();
		}
		this.setDelta();
		/*var slice = this.speed * this.delta;
		let rad = rftv(this);
		this.x += Math.cos(rad)  * slice;*/
		this.x = this.startX + this.delta;
		//this.y += Math.sin(rad) * slice;
	},
	now: undefined,
	delta: undefined,
	//then: undefined,
	setDelta: function() {
		this.now = Date.now();
		this.delta = easeInOutSine((this.now - this._started_), 0, (this.dx - this.startX), 5000);//(this.now - this.then) / 1000;
		//this.then = this.now;
	},
	moving: false,
	start: function() {
		this._started_ = Date.now();
		this.then = Date.now();
		this.moving = true;
	},
	end: function() {
		this.moving = false;
		console.log( Date.now() - this._started_, 'should be close to 5000' );
	}
};

let render = () => {
	ctx.fillStyle = '#ccc';
	ctx.fillRect(0, 0, w, h);

	ctx.beginPath();
	ctx.arc(obj.x, obj.y, 10, 0, Math.PI * 2);
	ctx.closePath();
	ctx.strokeStyle = 'red';
	ctx.stroke();
	obj.run();

	if(obj.moving){ requestAnimationFrame(render); }
};

document.body.appendChild(cvs);

obj.start();

render();

希望您更好地了解它的工作原理,祝您好运!

附加说明:我还颠倒了obj.start();render();并向requestAnimationFrame添加了一个条件,以避免无限循环。