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
相关产品推荐
相关产品推荐

