Chrome中requestAnimationFrame驱动的Canvas滚动卡顿问题求助
Chrome下requestAnimationFrame滚动时卡顿的解决办法
问题描述
我通过代码显示Canvas的帧间隔dt,当dt>20ms时内容填充红色。正常情况下我的电脑能支持requestAnimationFrame以60fps(约16ms)运行,但在Chrome浏览器中滚动时,画面频繁闪红、卡顿,即使轻微滚动也会触发该问题。绘制性能并非瓶颈,即使绘制大量元素也能流畅运行,仅滚动时会出现几帧卡顿。改用setInterval则无此问题,且Firefox浏览器中不存在该现象,求Chrome下的解决办法。
代码示例
<!DOCTYPE html> <html> <style> body, html { overflow: hidden; overscroll-behavior: none; } </style> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1, minimum-scale=1, viewport-fit=cover"> </head> <body style="padding:0;margin:0;background-color: #151515;pointer-events: none"> <script> class LCanvas{ constructor(){ this.canvas = document.createElement("canvas") this.ctx = this.canvas.getContext("2d") this.canvas.style.position = "fixed" this.canvas.style.top = "0px" this.canvas.style.left = "0px" this.canvas.style.pointerEvents = "none" document.body.appendChild(this.canvas) return(this) } fitScreenSize(){ this.canvas.width = window.innerWidth this.canvas.height = window.innerHeight } clear(){ this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height) } } var can = new LCanvas() can.clear() can.fitScreenSize() var lastTime = 0 function mainLoop(){ requestAnimationFrame(mainLoop) let now = performance.now() let dt = now-lastTime lastTime = now can.clear() can.ctx.font = "40px arial" can.ctx.fillStyle = dt>20?"red":"pink" can.ctx.fillText(dt,100,100) can.ctx.fillRect(400,100,dt*50,100) } requestAnimationFrame(mainLoop,true) </script> </body> </html>
解决办法
1. 给Canvas添加合成层优化样式
给Canvas元素添加will-change: transform样式,提示浏览器提前为该元素创建独立的合成层,避免滚动操作触发的重排重绘阻塞动画帧:
canvas { will-change: transform; }
2. 调整requestAnimationFrame的调用位置
将requestAnimationFrame的调用放在帧绘制逻辑的末尾,确保当前帧的所有绘制操作完成后再调度下一帧,减少帧调度冲突:
function mainLoop(){ let now = performance.now() let dt = now-lastTime lastTime = now can.clear() can.ctx.font = "40px arial" can.ctx.fillStyle = dt>20?"red":"pink" can.ctx.fillText(dt,100,100) can.ctx.fillRect(400,100,dt*50,100) // 将调度移到绘制逻辑之后 requestAnimationFrame(mainLoop) } requestAnimationFrame(mainLoop)
3. 开启Chrome线程化滚动优化
在Chrome地址栏输入chrome://flags/#enable-threaded-scrolling,开启Threaded Scrolling选项,重启浏览器后,滚动操作会由单独线程处理,降低对主线程动画的阻塞影响。
4. 拆分非关键绘制到空闲回调
如果存在非核心的绘制逻辑,可以将其放到requestIdleCallback中执行,避免占用动画帧的主线程时间:
function mainLoop(){ requestAnimationFrame(mainLoop) let now = performance.now() let dt = now-lastTime lastTime = now can.clear() // 优先执行关键绘制逻辑 can.ctx.font = "40px arial" can.ctx.fillStyle = dt>20?"red":"pink" can.ctx.fillText(dt,100,100) // 非关键逻辑放到浏览器空闲时执行 requestIdleCallback(() => { can.ctx.fillRect(400,100,dt*50,100) }) }
内容的提问来源于stack exchange,提问作者Ar25k
相关产品推荐
相关产品推荐

