定时 javascript 动画:结束时应该慢一些
Timing javascript animations: slower at the end then it should
我在尝试制作 Javascript 动画时遇到问题。我希望你们能帮助我,因为我在 Whosebug 或其他网站上找不到类似的问题。警告:我喜欢写作,所以这个话题可能有点长,我很抱歉。我只想把一切都说清楚。 TL;DR 版本在此 post.
的底部
我想做什么?
我正在构建自己的 Javascript 库(出于学习目的,因此我不想使用 jQuery 或任何其他库来解决此问题)。这个库有很多功能,动画就是其中之一。动画是特定元素的 CSS 的变化,但是是以一种平滑的方式。想象一下,您有一个元素的宽度 200px
,并且您希望将其设为 400px
,如果它不突然改变,而是温和地改变,它看起来会好很多。我设法制作了动画,所以这不是问题。
我的问题是什么?
动画有特定的时间范围。在此时间范围内,css 从一个值更改为另一个值。时间范围,就像我做的那样,有下一个计算:
let timeFraction = (time - start) / this.duration;
其中 start
是调用 animate()
方法的开始时间(稍后您会看到代码)。时间是动画运行时的时间,当时间和start相差duration
的时候,动画就结束了。
timeFraction
是自调用动画以来经过的时间量。此金额乘以要更改的 css 值。这听起来有点奇怪,我知道,所以让我举个例子:
我想再次将金额从 200px
更改为 400px
。这两个值之间的差异是 200px
。所以元素必须改变这个值。
动画时间范围为 3000 毫秒。所以动画必须在 3 秒内完成。 1.5 秒过去了(3 秒的一半),然后 200px
的一半应该加上 200px
的现有宽度。
let timeFraction = (1500 - 0) / 3000; // is 0.5
let cssAdded = timeFraction * 200; // 100px
所以当 3s 过去时,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)。
我的具体问题
为什么最后动画变慢了?这有什么关系?我上面解释的计算还是其他?还是我错过了一些重要的东西?请帮忙,因为我不知道是什么原因造成的。
我弄明白为什么动画最后比开始慢了。
正如我在问题中所述,每次调用 draw
方法时都会进行一次计算。同样,此计算检查了现有 css 属性 之间的差异,以进行动画处理并乘以 timing
变量(timing
变量保存动画的时间范围。所以当一半的动画时间过去后,timing
等于0.5,当动画结束时,timing
等于1)。
但是因为每次调用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;
}
}
并且我添加了一个计算差值的方法:
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;
}
这段代码被调用一次为每个动画 css 属性,所以这段代码会比计算每个动画帧的差异更快。
对于其他人来说,这是 timer
方法(作为对@Blindman67 问题的回答)
timer(timeFraction) {
switch(this.easing) {
case 'ease-in':
return timeFraction * timeFraction;
break;
case 'ease-out':
return timeFraction * (2 - timeFraction);
break;
case 'circ':
return 1 - Math.sin(Math.acos(timeFraction));
break;
case 'bounce':
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
}
break;
default:
return timeFraction;
break;
}
}
我希望其他想要制作 JS 动画的人发现这个问题 + 答案有用。感谢大家和我一起思考解决方案
我在尝试制作 Javascript 动画时遇到问题。我希望你们能帮助我,因为我在 Whosebug 或其他网站上找不到类似的问题。警告:我喜欢写作,所以这个话题可能有点长,我很抱歉。我只想把一切都说清楚。 TL;DR 版本在此 post.
的底部我想做什么?
我正在构建自己的 Javascript 库(出于学习目的,因此我不想使用 jQuery 或任何其他库来解决此问题)。这个库有很多功能,动画就是其中之一。动画是特定元素的 CSS 的变化,但是是以一种平滑的方式。想象一下,您有一个元素的宽度 200px
,并且您希望将其设为 400px
,如果它不突然改变,而是温和地改变,它看起来会好很多。我设法制作了动画,所以这不是问题。
我的问题是什么?
动画有特定的时间范围。在此时间范围内,css 从一个值更改为另一个值。时间范围,就像我做的那样,有下一个计算:
let timeFraction = (time - start) / this.duration;
其中 start
是调用 animate()
方法的开始时间(稍后您会看到代码)。时间是动画运行时的时间,当时间和start相差duration
的时候,动画就结束了。
timeFraction
是自调用动画以来经过的时间量。此金额乘以要更改的 css 值。这听起来有点奇怪,我知道,所以让我举个例子:
我想再次将金额从 200px
更改为 400px
。这两个值之间的差异是 200px
。所以元素必须改变这个值。
动画时间范围为 3000 毫秒。所以动画必须在 3 秒内完成。 1.5 秒过去了(3 秒的一半),然后 200px
的一半应该加上 200px
的现有宽度。
let timeFraction = (1500 - 0) / 3000; // is 0.5
let cssAdded = timeFraction * 200; // 100px
所以当 3s 过去时,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)。
我的具体问题
为什么最后动画变慢了?这有什么关系?我上面解释的计算还是其他?还是我错过了一些重要的东西?请帮忙,因为我不知道是什么原因造成的。
我弄明白为什么动画最后比开始慢了。
正如我在问题中所述,每次调用 draw
方法时都会进行一次计算。同样,此计算检查了现有 css 属性 之间的差异,以进行动画处理并乘以 timing
变量(timing
变量保存动画的时间范围。所以当一半的动画时间过去后,timing
等于0.5,当动画结束时,timing
等于1)。
但是因为每次调用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;
}
}
并且我添加了一个计算差值的方法:
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;
}
这段代码被调用一次为每个动画 css 属性,所以这段代码会比计算每个动画帧的差异更快。
对于其他人来说,这是 timer
方法(作为对@Blindman67 问题的回答)
timer(timeFraction) {
switch(this.easing) {
case 'ease-in':
return timeFraction * timeFraction;
break;
case 'ease-out':
return timeFraction * (2 - timeFraction);
break;
case 'circ':
return 1 - Math.sin(Math.acos(timeFraction));
break;
case 'bounce':
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
}
break;
default:
return timeFraction;
break;
}
}
我希望其他想要制作 JS 动画的人发现这个问题 + 答案有用。感谢大家和我一起思考解决方案