You need to enable JavaScript to run this app. Firefox中JS Canvas钟摆动画移动鼠标时卡顿掉帧的原因及解决
优惠活动
大模型
产品
解决方案
定价
更多

Firefox中JS Canvas钟摆动画移动鼠标时卡顿掉帧的原因及解决

问题背景

我是一名物理学家,出于兴趣自学JavaScript,正在开发钟摆动画效果。

  • 跨浏览器运行差异:Edge、Chrome浏览器中动画运行稳定,持续移动鼠标10-20秒的情况下统计到的掉帧次数仅5-6次;但在Firefox浏览器中,仅移动鼠标操作就会出现严重卡顿,持续移动鼠标时统计到的掉帧次数远超100次。
  • 自身开发基础:除少量C++编程经验外,属于JavaScript新手,查阅线上公开资料未找到对应解决方案,已附上最小可复现代码。

补充测试说明

2021.03.04 编辑内容:

  • 已在代码中添加FPS计数器,可统计连续掉1、2、3、4帧及更多帧的累计次数
  • 多设备版本交叉测试结果:在另一台设备的Firefox 75.0版本上运行无掉帧问题;在当前设备上全新安装Firefox 86.0版本后,移动鼠标时仍会出现掉帧
  • 性能检测结果:对运行过程做了性能录制,约4500ms时刻开始移动鼠标,可观测到移动鼠标过程中并非每帧都触发requestAnimationFrame回调,附性能录制截图:
    enter image description here

最小可复现代码

JavaScript 核心逻辑

var canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

var ctx = canvas.getContext('2d');

var color_obj = '#00AA00'

var pivot_x = window.innerWidth/2 , pivot_y = window.innerHeight/2;
var pivot_wx = window.innerHeight/25 , pivot_wy = pivot_wx;    
var pivot_ax = 0 , pivot_ay = 0;
var pivot_vx1 = 0 , pivot_vy1 = 0 , pivot_vx2 = 0 , pivot_vy2 = 0;

var ball_l = window.innerHeight/3;
var ball_ang = Math.PI/3;
var ball_sin , ball_cos;
var ball_x = pivot_x + ball_l * Math.sin(ball_ang);
var ball_y = pivot_y + ball_l * Math.cos(ball_ang);
var ball_r = window.innerHeight/25;

var ball_eps = 0;
var ball_om = 0;

var g = 10 * window.innerHeight/50;
var dt = 0.1;

var timer = new o_timer();

var i;

ctx.font = 20 +'px Arial';
                                                    
main();

// --------------------------------------------------- MAIN LOOP

function main ()
{   
    f_calculate();

    f_clearscreen();

    timer.tick();
    timer.display(0.7*window.innerWidth,10);

    f_draw();

    window.requestAnimationFrame(main);
}

//-------------------------------------------- PHYSICS

function f_calculate()    
{       
    ball_sin = (ball_x - pivot_x) / ball_l;
    ball_cos = (ball_y - pivot_y) / ball_l;
    
    ball_eps = ( - g * ball_sin - pivot_ax * ball_cos + pivot_ay * ball_sin ) / ball_l;
    ball_om = ball_om + ball_eps * dt;
    ball_ang = ball_ang + ball_om * dt;
    
    if(ball_ang > Math.PI) ball_ang = ball_ang - 2*Math.PI;
    else if(ball_ang < -Math.PI) ball_ang = ball_ang + 2*Math.PI;

    ball_x = pivot_x + ball_l * Math.sin(ball_ang);
    ball_y = pivot_y + ball_l * Math.cos(ball_ang);
}

// ---------------------------------------------- DRAWING

function f_clearscreen()                                            
{
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function f_draw ()
{
    // 绘制摆线
    ctx.beginPath();
    ctx.moveTo( pivot_x , pivot_y );
    ctx.lineTo( ball_x , ball_y );
    ctx.stroke();

    // 绘制固定支点
    ctx.fillStyle = color_obj;
    ctx.fillRect(pivot_x - pivot_wx/2, pivot_y - pivot_wy/2 , pivot_wx , pivot_wy);
    ctx.strokeRect(pivot_x - pivot_wx/2, pivot_y - pivot_wy/2 , pivot_wx , pivot_wy);

    // 绘制摆球
    ctx.fillStyle = color_obj;
    ctx.beginPath();
    ctx.arc(ball_x, ball_y, ball_r, 0, 2*Math.PI);
    ctx.closePath();
    ctx.fill();
    ctx.stroke(); 
}

// --------------------------------------------------------
function o_timer()
{   
    this.t_old = 0;
    this.t_new = 0;

    this.duration = 0;
    this.duration_frames = 0;           

    this.dt = 0;

    this.fps = 0;
    this.fps_displayT = 500;
    this.fps_counter_old = 0;
    this.fps_counter_new = 0;
    this.fps_tick_number = 0;

    this.fps_drop_count = [ 0 , 0 , 0 , 0 , 0 ];    

    this.tick = function()
    {
        // 计算相邻两次主循环的运行间隔
        this.t_old = this.t_new;
        this.t_new = performance.now();
        this.duration = this.t_new - this.t_old;
    
        // 为物理模拟提供真实时间步长
        this.dt = this.duration / 1000;
    
        // 每间隔500ms计算一次实时帧率
        this.fps_tick_number = this.fps_tick_number + 1;
        this.fps_counter_old = this.fps_counter_new;
        this.fps_counter_new = this.t_new % this.fps_displayT;
        if(this.fps_counter_old > this.fps_counter_new)
        {
            this.fps = 1000 * this.fps_tick_number / this.fps_displayT;
            this.fps_tick_number = 0;
        }

        // 按60Hz刷新率折算掉帧数量
        this.duration_frames = Math.round( 60 * this.duration / 1000 );
    
        // 分别统计掉1、2、3、4帧及连续掉5帧以上的累计次数
        if( this.duration_frames >= 2 && this.duration_frames <= 5 )
        {
            this.fps_drop_count[ this.duration_frames - 2 ] += 1;
        }
        else if ( this.duration_frames > 5 )
        {
            this.fps_drop_count[4] += 1;
        }
    }   

    this.line_height = 25;

    this.display = function(_x , _y )
    {
        ctx.fillStyle = '#00AA00';  
        ctx.fillText(this.duration + ' ms', _x , _y + this.line_height); 
        ctx.fillText(this.fps + ' fps',  _x , _y + 2*this.line_height );    
        ctx.fillText(this.fps_drop_count ,  _x , _y + 3*this.line_height ); 
    }
}

样式代码

html { 
  -ms-touch-action: none; /* 所有指针事件交由JavaScript代码处理 */
}
canvas {
  background: white;
  display: block;
}
body {
  margin: 0;
}

页面结构代码

<canvas></canvas>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 21:06:04