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回调,附性能录制截图:
最小可复现代码
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
相关产品推荐
相关产品推荐

