为什么每次点击按钮时Canvas螺旋桨动画的转速都会异常升高?
问题根源
转速随点击次数不断升高的核心原因是每次点击按钮都会启动一个新的requestAnimationFrame动画循环,但之前运行的旧循环没有被销毁,多个循环同时对画布做旋转累加,自然转速会越来越快,切换叶片数量时旧循环还在运行,就会影响下一个动画的转速。
修复方案
你需要先存储当前动画的帧ID,每次启动新动画前先取消上一个旧的动画帧,再把叶片绘制逻辑抽成通用方法,适配2-8片的叶片数量需求,不用为每个叶片数量单独写判断分支。
修改后JS代码
// 全局存储当前动画的帧ID,用于销毁旧动画 let currentAnimationId = null; function propellersNumber() { var number = document.getElementById('noOfPropellers').value; var container = document.getElementById('container'); // 切换螺旋桨数量时先销毁旧动画 if (currentAnimationId) { cancelAnimationFrame(currentAnimationId); currentAnimationId = null; } while (container.hasChildNodes()) { container.removeChild(container.lastChild); } for (let i = 0; i < number; i++) { container.appendChild(document.createTextNode("螺旋桨 " + (i + 1) + " 叶片数量: ")); var input = document.createElement("input"); input.id = "propeller" + i; input.type = "number"; input.defaultValue = 2; input.min = 2; input.max = 8; // 新增最大叶片数限制 input.name = "propeller" + i; container.appendChild(input); container.appendChild(document.createElement('br')); container.appendChild(document.createTextNode("螺旋桨 " + (i + 1) + " 转速档位: ")); var select = document.createElement('select'); select.id = "innerSelect" + i; var option; var inputdata = "慢||中||快"; inputdata.split('||').forEach(function(item, index) { option = document.createElement('option'); option.value = [2,6,10][index]; // 转速值直接存入value,减少后续判断逻辑 option.textContent = item; select.appendChild(option); }) container.appendChild(select); container.appendChild(document.createElement('br')); } } function switchBetween() { // 启动新动画前先销毁旧动画,避免多循环叠加 if (currentAnimationId) { cancelAnimationFrame(currentAnimationId); } // 此处可扩展支持多螺旋桨,当前先演示第一个螺旋桨逻辑 var el = document.getElementById('propeller0'); var bladeCount = parseInt(el.value); var speed = document.getElementById('innerSelect0'); var rotateSpeed = parseInt(speed.options[speed.selectedIndex].value); var canvas = document.getElementById("propellerOne"); var ctx = canvas.getContext("2d"); // 重置画布变换状态,清除之前的旋转位移残留 ctx.setTransform(1, 0, 0, 1, 0, 0); function animate() { currentAnimationId = requestAnimationFrame(animate); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = 1; // 通用叶片绘制逻辑,自动适配2-8片叶片 const anglePerBlade = 360 / bladeCount; for (let i = 0; i < bladeCount; i++) { ctx.save(); ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(Math.PI / 180 * (i * anglePerBlade)); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(canvas.width / 2, 0); ctx.stroke(); ctx.restore(); } // 统一做旋转累加 ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(Math.PI / 180 * rotateSpeed); ctx.translate(-canvas.width / 2, -canvas.height / 2); } currentAnimationId = requestAnimationFrame(animate); }
修改后HTML代码
原HTML外层嵌套的pre标签会导致内容渲染异常,移除即可:
<h2>输入螺旋桨数量</h2> <input type="number" id="noOfPropellers" name="propeller" min="0" value="0"> <button onclick="propellersNumber()">添加</button> <div id="container"></div> <button onclick="switchBetween()">应用设置</button> <div class="container"> <canvas class="propeller" id="propellerOne" height="120px"></canvas> </div>
关键修复点
- 用全局变量存储动画帧ID,每次启动新动画前销毁旧动画,彻底解决多循环叠加导致的转速升高问题
- 通用叶片绘制逻辑自动适配2-8片叶片,无需单独写每个叶片数的判断分支
- 新增画布变换重置逻辑,避免之前的旋转位移残留影响新动画
- 新增叶片数上限限制,转速值直接存入下拉选项属性,简化逻辑
内容的提问来源于stack exchange,提问作者danBogdan
相关产品推荐
相关产品推荐

