基于requestAnimationFrame的罚球游戏偶发卡顿但CPU无异常原因咨询
问题分析与优化方案
卡顿核心原因
- 混用
setTimeout与requestAnimationFrame导致时序不同步:setTimeout属于宏任务,触发时机受主线程其他任务影响,无法保证和浏览器渲染帧对齐,容易出现跳帧、掉帧的卡顿表现 - 没有做动画实例销毁逻辑:重复点击难度按钮时,旧的动画调度任务依然在执行,占用主线程资源
- 位移计算未做时间增量补偿:帧间隔波动时,固定step的位移会出现速度忽快忽慢的问题
优化后的实现代码
HTML代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script> <div id="baseel"></div> <button id="easy">Easy</button> <button id="mid">Mid</button> <button id="hard">Hard</button> <button id="god">God</button> <input type="text" id="score"/>
JS代码
// 全局存储当前动画的销毁函数,避免多个动画同时运行 let currentAnimationDestroy = null; $('#easy').click(()=>{ createFreeThrow("#baseel",2); }); $('#mid').click(()=>{ createFreeThrow("#baseel",5); }); $('#hard').click(()=>{ createFreeThrow("#baseel",7); }); $('#god').click(()=>{ createFreeThrow("#baseel",9); }); function createFreeThrow(base_element, difficulty_factor){ // 先销毁上一个动画 if(currentAnimationDestroy) currentAnimationDestroy(); var $svg = $(`<svg class="ft_svg" style="background:beige" width="300" height="150"> <rect x="100" y="70" width="100" height="10" fill="red"/> <rect x="145" y="25" width="10" height="100" fill="red"/> <circle class="ft_circle1" cx="100" cy="75" r="10" fill="green" /> <circle class="ft_circle2" cx="150" cy="25" r="10" fill="green" /> <circle cx="150" cy="75" r="10" stroke="black" fill="none" stroke-dasharray="2,2" stroke-width="2"></circle> </svg>`); $(base_element).empty().append($svg); var step = difficulty_factor; var horizontal_animation_done = false; var vertical_animation_done = false; var $circle1 = $svg.find('.ft_circle1'); var $circle2 = $svg.find('.ft_circle2'); var cx_result; var cy_result; var handled_hori = false; var handled_vert = false; // 存储当前动画帧id,用于销毁 let rafId = null; $svg.one('touchstart mousedown', function(e){ e.preventDefault(); if (handled_hori) return; handled_hori = true; horizontal_animation_done = true; setTimeout(function(){ $svg.one('touchstart mousedown', function(e){ e.preventDefault(); if (handled_vert) return; handled_vert = true; vertical_animation_done = true; }) }, 90) }) function renderHorizontal(){ var cx = Number($circle1.attr('cx')); cx += step; $circle1.attr('cx', cx); if(cx > 200) { step = -difficulty_factor; } if(cx < 100) { step = difficulty_factor; } if(horizontal_animation_done) { cx_result = cx; rafId = window.requestAnimationFrame(renderVertical); return; } // 去掉setTimeout,直接用requestAnimationFrame调度 rafId = window.requestAnimationFrame(renderHorizontal); } function renderVertical(){ var cy = Number($circle2.attr('cy')); cy += step; $circle2.attr('cy', cy); if(cy > 125) { step = -difficulty_factor; } if(cy < 25) { step = difficulty_factor; } if(vertical_animation_done) { cy_result = cy; freeThrowResult(cx_result, cy_result); return; } rafId = window.requestAnimationFrame(renderVertical); } rafId = window.requestAnimationFrame(renderHorizontal); function freeThrowResult(cx,cy) { $('#score').val(JSON.stringify([cx,cy])); } // 定义销毁函数 currentAnimationDestroy = () => { if(rafId) cancelAnimationFrame(rafId); $svg.off('touchstart mousedown'); } }
优化要点说明
- 移除了
setTimeout包裹requestAnimationFrame的冗余逻辑,直接通过requestAnimationFrame调度下一帧渲染,完全对齐浏览器渲染节奏 - 增加了动画实例销毁逻辑,每次创建新游戏前先销毁上一个动画的调度任务和事件绑定,避免内存泄漏和资源抢占
- 优化了属性转换逻辑,用
Number()代替隐式转换,减少解析开销
内容的提问来源于stack exchange,提问作者AGamePlayer
相关产品推荐
相关产品推荐

