HTML5 Canvas - 使用缓动功能旋转画布

问题描述 投票:0回答:1

问题:

通过

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" />

javascript animation html5-canvas rotation easing
1个回答
0
投票

解决方案

看来我已经找到了解决方案,从这篇 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" />

© www.soinside.com 2019 - 2024. All rights reserved.