问题:
通过HTML5 canvas 动画进行 Easing 旋转,在动画结束时停在所需的角度(或旋转角度)。本质上,在整个动画的时间跨度内(平滑地)拉伸这个缓动动画;不受不确定的 fps 值的影响。
外卖:
_rotate
函数可以正常运行,可以在一次调用中跳转到特定的 degree
值。
当您想要顺利地
_progress
通过此旋转,从 0
到 _desiredAngle
时,就会出现问题。
尝试通过简单地将动画的
_desiredAngle
与当前 _progress
(0 - 1) 相乘来获得每个半度,不会产生平滑过渡所需的正确半度。这是我思考的 ATM,我可能看错了。
具有
_fpsEstimate
值确实会缩小 90 度平移的窗口。这里需要注意的是,该动画现在与该幻数相关联,并且根据负载(对于多个动画),该值将会改变。为了在负载下保持准确性,我试图找到一种更精确的方法来实现类似的结果;无需除以步数计数器或不断变化的 fps。
示例:
_time
:3000
_fpsEstimate
:65
let _canvas = document.querySelector ( 'canvas' );
let _context = _canvas.getContext ( '2d' );
let _coordinates = { x: 100, y: 100 }
let _desiredAngle = 90;
let _time = 3000;
let _fpsEstimate = 65;
//// FUNCTIONS ///////////////////////////////////////
/**
* Timing function for easeInSine
* @param {number} timeFraction Timing fraction
* @return {number} Progress
*/
function _timingFunction ( timeFraction )
{
return 1 - Math.cos ( ( timeFraction * Math.PI ) / 2 );
}
/**
* Rotate canvas
* @param {number} degree Degree to rotate
* @param {Object} coordinates X & Y coordinates
*/
function _rotate ( degree, coordinates )
{
let [ _x, _y ] = [ coordinates.x, coordinates.y ];
_context.save ( );
_context.translate ( _x, _y );
_context.rotate ( degree * Math.PI / 180 );
_context.translate ( -_x, -_y );
}
/**
* Animate object
*/
function _animate ( )
{
let _start = performance.now ( );
let _width = 50;
let _height = 50;
requestAnimationFrame (
function animate ( time )
{
_context.clearRect ( 0, 0, 500, 500 );
let _timeFraction = ( time - _start ) / _time; // timeFraction goes from 0 to 1
let _progress = _timingFunction ( _timeFraction ); // calculate the current animation state
let _rotateAmount = _progress * _desiredAngle / _fpsEstimate; // calculate distance between current progress & desire angle
_rotate ( _rotateAmount, _coordinates );
_context.strokeRect ( _coordinates.x - ( _width / 2 ), _coordinates.y - ( _height / 2 ), _width, _height );
( _timeFraction < 1 )
? requestAnimationFrame ( animate )
: console.log ( 'complete !' );
}
);
}
//// INIT ////////////////////////////////////////////
_animate ( );
<canvas width='500' height='500'></canvas>
_time
:2000
_fpsEstimate
:65
let _canvas = document.querySelector ( 'canvas' );
let _context = _canvas.getContext ( '2d' );
let _coordinates = { x: 100, y: 100 }
let _desiredAngle = 90;
let _time = 2000;
let _fpsEstimate = 65;
//// FUNCTIONS ///////////////////////////////////////
/**
* Timing function for easeInSine
* @param {number} timeFraction Timing fraction
* @return {number} Progress
*/
function _timingFunction ( timeFraction )
{
return 1 - Math.cos ( ( timeFraction * Math.PI ) / 2 );
}
/**
* Rotate canvas
* @param {number} degree Degree to rotate
* @param {Object} coordinates X & Y coordinates
*/
function _rotate ( degree, coordinates )
{
let [ _x, _y ] = [ coordinates.x, coordinates.y ];
_context.save ( );
_context.translate ( _x, _y );
_context.rotate ( degree * Math.PI / 180 );
_context.translate ( -_x, -_y );
}
/**
* Animate object
*/
function _animate ( )
{
let _start = performance.now ( );
let _width = 50;
let _height = 50;
requestAnimationFrame (
function animate ( time )
{
_context.clearRect ( 0, 0, 500, 500 );
let _timeFraction = ( time - _start ) / _time; // timeFraction goes from 0 to 1
let _progress = _timingFunction ( _timeFraction ); // calculate the current animation state
let _rotateAmount = _progress * _desiredAngle / _fpsEstimate; // calculate distance between current progress & desire angle
_rotate ( _rotateAmount, _coordinates );
_context.strokeRect ( _coordinates.x - ( _width / 2 ), _coordinates.y - ( _height / 2 ), _width, _height );
( _timeFraction < 1 )
? requestAnimationFrame ( animate )
: console.log ( 'complete !' );
}
);
}
//// INIT ////////////////////////////////////////////
_animate ( );
<canvas width='500' height='500'></canvas>
CSS 示例
这可以通过 CSS 轻松完成。但是,我正在通过画布寻找相同的解决方案。
.rotate { animation: rotation 2s; }
.easing { animation-timing-function: easeIn; }
.infinite { animation-iteration-count: 1; }
@keyframes rotation
{
from { transform: rotate(0deg); }
to { transform: rotate(90deg); }
}
<img src="https://pngimg.com/uploads/square/square_PNG35.png" class="rotate linear infinite" width="75" height="75" />
解决方案
看来我已经找到了解决方案,从这篇 8 年前的帖子使用 Tween 绕世界轴精确旋转。
关键看似简单...追踪每半圈的累积
_rotation
(_rotate
),追踪每个角度之间的距离;即 0 - _angle
。
注意: 这个
_rotation
值也应该在每个动画开始时清零。
然后,通过将
_rotate
乘以所需的 _progress
减去当前 _angle
距离,计算需要多少 _rotation
动画。
let _rotate = _progress * ( _angle - _rotation );
工作解决方案
let _canvas = document.querySelector ( 'canvas' );
let _context = _canvas.getContext ( '2d' );
let _coordinates = { x: 100, y: 100 }
let _angle = 90;
let _time = 2000;
let _rotation = undefined; // tracks n rotation(s) through each cycle
//// FUNCTIONS ///////////////////////////////////////
/**
* Timing function for easeInSine
* @param {number} timeFraction Timing fraction
* @return {number} Progress
*/
function _timingFunction ( timeFraction )
{
return 1 - Math.cos ( ( timeFraction * Math.PI ) / 2 );
}
/**
* Rotate canvas
* @param {number} degree Degree to rotate
* @param {Object} coordinates X & Y coordinates
*/
function _rotateCanvas ( degree, coordinates )
{
let [ _x, _y ] = [ coordinates.x, coordinates.y ];
_context.save ( );
_context.translate ( _x, _y );
_context.rotate ( degree * Math.PI / 180 );
_context.translate ( -_x, -_y );
}
/**
* Animate object
*/
function _animate ( )
{
let _start = performance.now ( );
let _width = 50;
let _height = 50;
_rotation = 0;
requestAnimationFrame (
function animate ( time )
{
_context.clearRect ( 0, 0, 500, 500 );
let _timeFraction = ( time - _start ) / _time; // timeFraction goes from 0 to 1
let _progress = _timingFunction ( _timeFraction ); // calculate the current animation state
let _rotate = _progress * ( _angle - _rotation ); // calculate distance between current progress & desire angle
_rotateCanvas ( _rotate, _coordinates );
_rotation += _rotate; // <== sum rotational distance through each cycle
_context.strokeRect ( _coordinates.x - ( _width / 2 ), _coordinates.y - ( _height / 2 ), _width, _height );
( _timeFraction < 1 )
? requestAnimationFrame ( animate )
: console.log ( 'complete !' );
}
);
}
//// INIT ////////////////////////////////////////////
_animate ( );
<canvas width='500' height='500'></canvas>
最后的想法
此解决方案似乎在使用不同的缓动公式时保持缓动梯度,同时准确地绑定每个动画的长度(在
_time
中);创建平滑过渡,同时使用每个动画循环缩放每种过渡类型。
CSS 比较
.rotate { animation: rotation 2s; }
.easing { animation-timing-function: easeIn; }
.infinite { animation-iteration-count: 1; }
@keyframes rotation
{
from { transform: rotate(0deg); }
to { transform: rotate(90deg); }
}
<img src="https://pngimg.com/uploads/square/square_PNG35.png" class="rotate linear infinite" width="75" height="75" />