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

如何正确结合ResizeObserver与requestAnimationFrame解决画布闪烁问题

解决ResizeObserver结合requestAnimationFrame时画布闪烁问题

先看正常运行的ResizeObserver用法,此时画布尺寸变化时能正常重绘:

<canvas></canvas>
html, body {
 height: 100%;
 margin: 0;
}
canvas {
 width: 100%;
 height: 100%;
 display: block;
}
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

function draw() {
   const { width, height } = canvas;

   ctx.clearRect(0, 0, width, height);
   ctx.save();
   ctx.translate(width / 2, height / 2);
   const size = Math.min(width, height);
   ctx.beginPath();
   ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
   ctx.fill();
   ctx.restore();
}

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    const canvas = entry.target;
    canvas.width = entry.contentBoxSize[0].inlineSize;
    canvas.height = entry.contentBoxSize[0].blockSize;
    draw();
  }
})
observer.observe(canvas);

但当把绘制逻辑移到requestAnimationFrame循环并添加动效后,调整窗口大小时画布会出现闪烁:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

function draw() {
   const { width, height } = canvas;

   ctx.clearRect(0, 0, width, height);
   ctx.save();
   ctx.translate(width / 2, height / 2);
   const size = Math.min(width, height);
   ctx.beginPath();
   ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
   ctx.fill();
   ctx.rotate(performance.now() * 0.001);
   ctx.strokeStyle = 'red';
   ctx.strokeRect(-5, -5, size / 4, 10);
   ctx.restore();
}

function rAFLoop() {
  draw();
  requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    const canvas = entry.target;
    canvas.width = entry.contentBoxSize[0].inlineSize;
    canvas.height = entry.contentBoxSize[0].blockSize;
  }
})
observer.observe(canvas);

问题原因

闪烁的核心是ResizeObserver回调的执行时机和requestAnimationFrame的帧循环不同步:

  • 修改canvas.width/height会直接清空画布内容
  • 如果ResizeObserver回调在当前帧的draw执行之后触发,修改尺寸后要等到下一帧才会重新绘制,中间会出现空白帧,导致视觉闪烁

解决方法

把尺寸更新逻辑移到requestAnimationFrame的绘制循环里,确保尺寸修改和绘制在同一帧完成,避免空白帧出现。具体做法:

  1. 在ResizeObserver回调中仅记录最新的画布尺寸,不直接修改canvas属性
  2. 在draw函数中检查尺寸是否变化,若变化则更新canvas的宽高,再执行绘制

修改后的代码如下:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// 存储最新的目标尺寸
let targetWidth = canvas.offsetWidth;
let targetHeight = canvas.offsetHeight;

// 初始化画布尺寸
canvas.width = targetWidth;
canvas.height = targetHeight;

function draw() {
  // 检查尺寸是否变化,若变化则更新画布
  if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
    canvas.width = targetWidth;
    canvas.height = targetHeight;
  }

  const { width, height } = canvas;
  ctx.clearRect(0, 0, width, height);
  ctx.save();
  ctx.translate(width / 2, height / 2);
  const size = Math.min(width, height);
  ctx.beginPath();
  ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
  ctx.fill();
  ctx.rotate(performance.now() * 0.001);
  ctx.strokeStyle = 'red';
  ctx.strokeRect(-5, -5, size / 4, 10);
  ctx.restore();
}

function rAFLoop() {
  draw();
  requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);

const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    // 仅更新目标尺寸,不修改画布
    targetWidth = entry.contentBoxSize[0].inlineSize;
    targetHeight = entry.contentBoxSize[0].blockSize;
  }
})
observer.observe(canvas);

额外优化

还可以添加防抖逻辑,避免窗口调整过程中频繁触发尺寸判断,但上面的方法已经能解决核心的闪烁问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 19:24:52