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

Canvas汽车旋转动画无法按序显示,断点调试正常

问题描述

我用Canvas实现汽车在背景图上的旋转效果,按下"s"键触发旋转序列。为了减慢旋转速度,我写了个fSleep函数延迟1秒,但运行时汽车图像完全没变化,只有在fSleep函数处设断点并按F5才能看到旋转效果。

代码

HTML

<html>
    <body>
        <div id="canvasContainer">
            <canvas id="canvas" width="900" height="520"></canvas>
        </div>
        <script src="image.js"></script>
</body>
</html>

JavaScript

//------------------------------
const gcCanvas = document.getElementById('canvas');
gcCanvas.width = 600;
gcCanvas.height = 300;

const gcContext = gcCanvas.getContext('2d');

const gcBackgroundImage = new Image();
const gcBackgroundWidth = gcCanvas.width;
let glBackgroundHeight; 

const gcCarImage = new Image();
const gcCarImageWidth = 40; 
const gcCarImageHeight = 100; 
let glCarTopLeftX; 
let glCarTopLeftY; 

let glDegrees = 0;
let glArrowFlag = false;
//------------------------------
//++++++++++++++++++++++++++++++
//------------------------------
document.addEventListener('DOMContentLoaded', (ev) => 
{
    gcBackgroundImage.onload = function ()
    {
        gcCarImage.onload = function () 
        {
            const cAspectRatio = gcBackgroundImage.naturalWidth / gcBackgroundImage.naturalHeight;
            glBackgroundHeight = gcCanvas.height = gcBackgroundWidth / cAspectRatio; 
            gcContext.drawImage(gcBackgroundImage, 0, 0, gcBackgroundWidth, glBackgroundHeight); 
            glCarTopLeftX = gcBackgroundWidth / 2 - gcCarImageWidth / 2; 
            glCarTopLeftY = glBackgroundHeight / 2 - gcCarImageHeight / 2; 
            gcContext.drawImage(gcCarImage, glCarTopLeftX, glCarTopLeftY, gcCarImageWidth, gcCarImageHeight);
        }
        gcCarImage.src = "Car.jpg"; 
    }
    gcBackgroundImage.src = "Streets.jpg";
}
)//document.addEventListener('DOMContentLoaded'
//------------------------------
//------------------------------
document.body.addEventListener("keydown", e =>
{
    let lRenderFlag = false;
    e.preventDefault();
    switch(e.key) {
        case 's':
            glDegrees = 0;
            for (let ii = 0; ii < 360; ii++) {
                glDegrees = ii;
                let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
                let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;  
                fRender(lCarCenterX, lCarCenterY, glDegrees);
                fSleep(1000);
            } 
            break;
    }
}
)//document.body.addEventListener("keydown"
//------------------------------
//------------------------------
function fSleep(num) 
{
    let now = new Date();
    let stop = now.getTime() + num;
    while(true) {
        now = new Date();
        if(now.getTime() > stop) return;
    }
}//fSleep
//------------------------------
//------------------------------
function fRender(lCarCenterX, lCarCenterY, glDegrees) 
{
    gcContext.clearRect(0, 0, gcCanvas.width, gcCanvas.height);
    gcContext.drawImage(gcBackgroundImage, 0, 0, gcBackgroundWidth, glBackgroundHeight);
    let lRadians = Math.PI / 180 * glDegrees;
    gcContext.translate(lCarCenterX, lCarCenterY);
    gcContext.rotate(lRadians);
    gcContext.drawImage(gcCarImage, -gcCarImageWidth / 2, -gcCarImageHeight / 2, gcCarImageWidth, gcCarImageHeight);
    gcContext.rotate(-lRadians);
    gcContext.translate(-lCarCenterX, -lCarCenterY);    
}//fRender
//------------------------------

问题原因

你写的fSleep是同步阻塞的循环,会彻底卡死浏览器的事件循环。浏览器的UI渲染和JS代码执行共用同一个线程,同步阻塞函数会让浏览器完全没时间更新Canvas画面,直到整个for循环跑完,最后只显示最后一次渲染的结果。而设置断点时,浏览器会在断点暂停JS执行,同时更新UI,所以能看到每一步的旋转效果。

解决方案

方法1:用setTimeout实现分步旋转

把循环改成用setTimeout逐个触发渲染,这样不会阻塞事件循环,浏览器能及时更新UI:

document.body.addEventListener("keydown", e => {
    let lRenderFlag = false;
    e.preventDefault();
    switch(e.key) {
        case 's':
            let ii = 0;
            const rotateCar = () => {
                if (ii >= 360) return;
                glDegrees = ii;
                let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
                let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;  
                fRender(lCarCenterX, lCarCenterY, glDegrees);
                ii++;
                setTimeout(rotateCar, 1000); // 每秒旋转1度
            };
            rotateCar();
            break;
    }
});

同时删掉原来的fSleep函数即可。

方法2:用requestAnimationFrame(更推荐)

requestAnimationFrame是浏览器专为动画设计的API,能让动画和浏览器刷新频率同步,效果更流畅稳定:

document.body.addEventListener("keydown", e => {
    let lRenderFlag = false;
    e.preventDefault();
    switch(e.key) {
        case 's':
            glDegrees = 0;
            let lastTime = 0;
            const rotateCar = (timestamp) => {
                if (glDegrees >= 360) return;
                // 控制每秒旋转1度(每1000ms更新一次)
                if (!lastTime || timestamp - lastTime >= 1000) {
                    lastTime = timestamp;
                    let lCarCenterX = glCarTopLeftX + gcCarImageWidth / 2;
                    let lCarCenterY = glCarTopLeftY + gcCarImageHeight / 2;  
                    fRender(lCarCenterX, lCarCenterY, glDegrees);
                    glDegrees++;
                }
                requestAnimationFrame(rotateCar);
            };
            requestAnimationFrame(rotateCar);
            break;
    }
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:42:02