You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在HTML5 Canvas中使用缓动函数实现精准旋转动画?

实现Canvas精准缓动旋转动画(不依赖FPS)

核心修正点

你的代码问题出在旋转角度的计算逻辑上:错误引入_fpsEstimate作为除数,且误解了Canvas旋转的工作方式。Canvas每次绘制都是基于初始画布状态的,因此你需要直接设置当前帧的总旋转角度,而非增量角度。

正确实现思路

  1. 用performance.now()获取精准时间戳,计算当前动画已进行时间占总时长的比例(timeFraction,范围0-1)。
  2. 通过缓动函数将timeFraction转换为带缓动效果的进度值(_progress)。
  3. 用_progress * _desiredAngle得到当前帧应旋转的总角度,直接传入_rotate函数即可。
  4. 动画结束(timeFraction >= 1)时,强制设置旋转角度为_desiredAngle,确保精准到位。

修改后的完整代码

let _canvas       = document.querySelector('canvas');
let _context      = _canvas.getContext('2d');
let _coordinates  = { x: 100, y: 100 };
let _desiredAngle = 90;
let _time         = 3000;

////    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 = Math.min((time - _start) / _time, 1); // 限制在0-1范围内
        let _progress = _timingFunction(_timeFraction);
        let _rotateAmount = _progress * _desiredAngle; // 直接计算当前总旋转角度

        _rotate(_rotateAmount, _coordinates);
        _context.strokeRect(
            _coordinates.x - (_width / 2),
            _coordinates.y - (_height / 2),
            _width,
            _height
        );

        if (_timeFraction < 1) {
            requestAnimationFrame(animate);
        } else {
            // 动画结束时强制设置为目标角度,确保精准
            _context.clearRect(0, 0, 500, 500);
            _rotate(_desiredAngle, _coordinates);
            _context.strokeRect(
                _coordinates.x - (_width / 2),
                _coordinates.y - (_height / 2),
                _width,
                _height
            );
            console.log('complete !');
        }
    });
}

////    INIT    ////////////////////////////////////////////
_animate();
<canvas width='500' height='500'></canvas>

关键说明

  • 完全移除_fpsEstimate,动画进度仅依赖精准时间计算,不受帧率波动影响。
  • 用Math.min限制_timeFraction不超过1,避免动画结束后进度超出范围。
  • 动画结束时额外绘制一次目标角度的图形,彻底消除时间计算可能带来的微小偏差。
  • 缓动函数_timingFunction实现了和CSS easeInSine一致的先慢后快效果,与你提供的CSS示例行为匹配。

内容的提问来源于stack exchange,提问作者Justin Byrne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 04:47:07