移动鼠标时 Firefox 中的 Jerky javascript 动画

问题描述

我是一名物理学家,我开始学习 JS 是为了好玩。我正在尝试编写钟摆的动画。当我在 Firefox 中运行它时,当我只移动鼠标时它会变得非常生涩,但是当我在 Edge 或 Chrome 中运行它时,问题不会发生。除了知道一点 C++ 之外,我还是个菜鸟!我试图在网上阅读,但找不到答案。我在下面添加一个最小的例子。为什么会发生这种情况,我该如何阻止?

我实际上还添加一个函数(未包含在下面的示例中),该函数计算主循环的后续重复之间经过的时间,并在此基础上显示掉帧数。当我运行程序 10-20 秒并不断移动鼠标时,在 Edge 或 Chrome 中只有 5-6 个丢帧。在 Firefox 中有超过 100 个

Edit 04.03.2021:这是带有 fps 计数器的代码。最后一行是为了显示丢帧的数量:连续 1、2、3、4 或更多。我还在 Firefox 上的另一台计算机上检查了该程序(如果重要,请使用 75.0 版) - 没有掉帧!在这台计算机上,我已经干净地重新安装了 Firefox (86.0) - 当我移动鼠标时,仍然有这些掉落。

在这里我运行了性能检查。在大约 4500 毫秒时,我开始移动鼠标。我不太了解它,但我可以说 requestAnimationFrame 甚至没有在每一帧中都被调用

enter image description here

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,canvas.width,canvas.height);
}

function f_draw ()
{
                                        // Draws string

    ctx.beginPath();
    ctx.moveto( pivot_x,pivot_y );
    ctx.lineto( ball_x,ball_y );
    ctx.stroke();

                                        // Draws pivot

    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_wy);

                                        // Draws ball
    
    ctx.fillStyle = color_obj;
    ctx.beginPath();
    ctx.arc(ball_x,ball_y,ball_r,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 ];    

    this.tick = function()
    {
                    // calculates duration between subsequent ticks
        this.t_old = this.t_new;
        this.t_new = performance.Now();
        this.duration = this.t_new - this.t_old;
    
                            // provides realistic dt for physics
        this.dt = this.duration / 1000;
    
                    // calculates tick number per every fps display 
        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;
        }
            // calculates the duration in frames (assuming 60 Hz)
                                                            
        this.duration_frames = Math.round( 60 * this.duration / 1000 );
    
                    // counts dropped frames (1,2,3,4,more)
        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;
        }
    }   
                                    // for displaying fps etc.
    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',_y + 2*this.line_height );    
        ctx.fillText(this.fps_drop_count,_y + 3*this.line_height ); 
    }
}
html { 
  -ms-touch-action: none; /* Direct all pointer events to JavaScript code. */
}
canvas {
  background: white;
  display: block;
}
body {
  margin: 0;
}
<canvas></canvas>

解决方法

这里有一些更新,供将来可能阅读本文的任何人使用。

丢帧的原因不在代码中。事实上,当我移除钟摆并且只留下带有 fps 计数器的空画布时,我得到了相同的帧丢失。

原因是我在 Surface Pro 上使用电池运行它。当 Surface 插入电源时,没有掉帧。我还使用 Surface 电池检查了不同的浏览器,我在 Chrome 上的丢帧率减少了。所以原因一定是我电脑上的一些省电选项以及浏览器的某种有限的效率。

问题仍然是我可以做些什么来最大限度地减少这种情况。也许创建更小的画布,在上面画画然后放大?或者以某种方式仅在情况发生变化的区域更新屏幕?我还没试过。但这是另一个线程的主题。