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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 15:06:01