我在尝试制作Javascript动画时遇到问题。我希望你们可以帮助我,因为我在Stackoverflow或其他网站上找不到类似的问题。警告:我喜欢写作,所以也许这个主题有点长,我很抱歉。我只想让一切都清楚。 TL; DR版本位于本文的底部。
我想做什么?
我正在构建自己的Javascript库(出于学习目的,因此我不想使用jQuery或任何其他库来解决此问题)。该库具有许多功能,动画就是其中之一。动画是特定元素的CSS的变化,但是以平滑的方式。想象一下,你有一个元素的宽度为200px
而你想要它400px
,如果它不会发生变化,而是相当温和地看起来好多了。我设法制作动画,所以这不是问题。
我的问题是什么?
动画具有特定的时间范围。在此时间范围内,css从一个值变为另一个值。像我做的那样,时间框架有下一个计算:
let timeFraction = (time - start) / this.duration;
其中start
是调用animate()
方法的开始时间(稍后会看到代码)。时间是动画运行的时间,当时间和开始时间与duration
的数量不同时,动画结束。
timeFraction
是自调用动画以来传递的时间量。这个ammount乘以要改变的css值。这听起来有点奇怪,我知道,所以让我举一个例子:
我想再次从200px
更改为400px
。这两个值之间的差异是200px
。因此元素必须改变这个值
动画时间范围是3000毫秒。所以动画必须在3秒内完成。过去1.5秒(3秒的一半),然后200px
的一半应该加上200px
的现有宽度。
let timeFraction = (1500 - 0) / 3000; // is 0.5
let cssAdded = timeFraction * 200; // 100px
因此,当3分过去时,timeFraction
应为1
,这将是完整的200px
。
这就像一个魅力。动画运行流畅,但是当它即将结束时,动画会慢得多。这是一个例子:
虽然计算结果仍然相同。我只是希望它能够顺利改变,直到时间框架结束。
我的代码是什么?
我正在使用EcmaScript 6和vanilla JS。
动画方法:
animate(css) {
if(this !== undefined && css) {
this.css = css;
}
let start = performance.now();
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / this.duration;
if( timeFraction > 1 ) timeFraction = 1;
let timing = this.timer(timeFraction);
this.draw(timing);
if ( timing < 1 ) {
requestAnimationFrame(animate.bind(this));
}
}.bind(this));
}
计时器方法:
timer(timeFraction) {
return timeFraction;
}
绘制方法:
draw(timing) {
for(let property in this.css) {
let newValue = window.getComputedStyle(this.element)[property];
let match = /\d+(px|em|%|rem)/.exec(newValue);
if(match !== null) {
newValue = match[0].replace(match[1], '');
this.element.style[property] = Math.floor(parseInt(window.getComputedStyle(this.element)[property].replace('px', '')) - (timing * (newValue - parseInt(this.css[property].replace('px', ''))))) + match[1];
}
else {
this.element.style[property] -= timing * (window.getComputedStyle(this.element)[property] - this.css[property]);
}
}
}
我尝试了什么?
首先,我试图搜索制作javascript动画的人的例子。我尝试了在动画时间范围内计算css的方法,但问题没有解决。我尝试将performance.now()
更改为new Date().getTime()
并从那里进一步计算,但仍然出现同样的问题。所以我不知道为什么动画并不总是以相同的速度,而应该是。你能帮忙吗?
TL; DR版
我在Javascript中制作动画。动画是线性的(总是以相同的速度),但是动画结束时动画的速度较慢(参见本主题中的GIF)。
我的具体问题
为什么动画最后会变慢?那与此有什么关系?我上面解释的计算还是其他什么?还是我错过了重要的事情?请帮忙,因为我不知道是什么导致了这一点。
答案 0 :(得分:0)
我弄清楚为什么动画最终比起初要慢。
正如我在我的问题中所述,每次调用draw
方法时都会进行计算。同样,此计算检查了要动画的现有css属性与timing
变量相乘的差异(timing
变量保存动画的时间帧。因此,当动画时间的一半通过时,{ {1}}等于0.5,当动画完成时,timing
为1)。
但是因为差异计算每时间调用timing
方法,所以差异每次都不同。最后,当动画几乎完成时,这种差异非常小。虽然差异变小,但draw
也变小了
这导致每次添加较小的差异。 (所以即使从一开始它变慢,但你不会注意到它)。
我更改了代码以计算动画开头的差异,timing * difference
现在乘以该差异,而不是每次循环再次获取差异。 (除此之外,代码变得更清晰,更容易阅读/理解。
动画方法:
timing
绘制方法:
animate(css) {
if(typeof css === 'object') {
for(let property in css) {
this.cssDiff[property] = this.calculateDifference(property, css[property])
}
}
else if(arguments.length === 2) {
this.cssDiff[arguments[0]] = this.calculateDifference(arguments[0], arguments[1]);
}
else {
return;
}
//The rest of the code, same as in my question...
}
我添加了一种计算差异的方法:
draw(timing) {
for(let property in this.cssDiff) {
this.element.style[property] = this.cssDiff[property].start + (timing * this.cssDiff[property].value) + this.cssDiff[property].unit;
}
}
对于每个动画css属性,这段代码被调用一次,因此这段代码将比计算每个动画帧的差异更快。
对于其他人来说,这是calculateDifference(property, cssValue) {
let existingCSS = window.getComputedStyle(this.element)[property];
let match = /(\d+)(px|em|%|rem)/.exec(existingCSS);
let differenceMap = {value: 0, unit: 0, start: 0};
if(match !== null) {
differenceMap.start = Number(existingCSS.replace(/px|em|%|rem/g, ''));
differenceMap.value = Number(cssValue.replace(/px|em|%|rem/g, '')) - Number(match[1]);
differenceMap.unit = match[2];
}
else {
differenceMap.start = Number(existingCSS);
differenceMap.value = (typeof cssValue === 'string' ? Number(cssValue.replace(/px|em|%|rem/g, '')) : cssValue) - Number(existingCSS);
}
return differenceMap;
}
方法(作为@ Blindman67问题的答案)
timer
我希望其他想要制作JS动画的人发现这个问题+答案很有用。感谢大家与我一起思考提出解决方案