在Javascript中等待重绘

4
我试图在更改元素背景之后运行一些代码。我想在背景被视觉更改后运行代码,而不仅仅是内部更改。因此,我想要等待重新绘制。我尝试使用requestAnimationFrame(rAF)来实现这一目标,但似乎不是正确的方法,因为代码在背景颜色在视觉上更改之前执行(虽然只有几毫秒)。
我尝试使用以下代码来实现我的目标(但没有成功):
  requestAnimationFrame(() => {
    document.getElementsByTagName("body")[0].style.backgroundColor =
      "rgb(0, 75, 75)";
      requestAnimationFrame(() => {
        socket.emit("notify-black");
      });
  });

等待重绘的正确方法是什么?

2个回答

4

requestAnimationFrame会在下一次重绘之前运行回调函数。将要运行的代码放在setTimeout中,因为定时器回调几乎会在重绘完成后立即运行。

你也可以使用document.body替代document.getElementsByTagName("body")[0]

requestAnimationFrame(() => {
  document.body.style.backgroundColor = "rgb(0, 75, 75)";
  requestAnimationFrame(() => {
    setTimeout(() => {
      socket.emit("notify-black");
    });
  });
});

本示例使用 alert(会阻塞代码执行)代替 socket.emit 进行演示:

requestAnimationFrame(() => {
  document.body.style.backgroundColor = "rgb(0, 75, 75)";
  requestAnimationFrame(() => {
    setTimeout(() => {
      alert("notify-black");
    });
  });
});

以下是使用html2canvas进行屏幕截图的另一个代码示例。 打开example.com,打开您的控制台,并运行以下命令:

const script = document.body.appendChild(document.createElement('script'));
script.src = 'https://html2canvas.hertzen.com/dist/html2canvas.js'
script.onload = () => {
  requestAnimationFrame(() => {
    document.body.style.backgroundColor = "rgb(0, 75, 75)";
    requestAnimationFrame(() => {
      setTimeout(() => {
        html2canvas(document.querySelector("body")).then(canvas => {
          document.write('<img src="'+canvas.toDataURL("image/png")+'"/>');
        });
      });
    });
  });
};

很遗憾,这不起作用。发出的信号会发送到服务器并转发给另一个客户端,该客户端在检索时运行一个函数。由于某种原因,发送和接收此信号比本地实际更改背景颜色要快。现在,我只设置了500毫秒的超时,但我想消除这种猜测因素。 - Bram Vanbilsen
这应该不是问题 - 请查看使用 alert 阻塞的代码片段。当超时回调运行时,背景应始终重新绘制(requestAnimationFrame 总是在重绘之前运行,setTimeout 回调必然在其后运行)。 - CertainPerformance
然而,emit 的接收端从来没有自己随机启动其方法。只有在接收到 emit 后它才运行其方法,这是我调试过的结果。但我不确定原因。我仔细检查了一遍,发现 emit 只到达一次,因此没有其他客户端尝试进行通信。 - Bram Vanbilsen
确实,我已经运行了颜色检测。当接收到发射信号时,会拍摄一张照片。从这个照片中,我知道背景还没有视觉上更新。 - Bram Vanbilsen
我添加了另一个示例,对我来说仍然完美运行 - alert 已经显示并且背景已经绘制,html2canvas 也是如此。我认为你的问题在其他地方,问题中的代码似乎按预期工作。 - CertainPerformance
显示剩余2条评论

1

我找到的最好的可靠且有效的答案,包括解释,建议阅读这个https://www.webperf.tips/tip/measuring-paint-time/

以备参考,万一链接在某些时候不再可用:

/**
 * Runs `callback` shortly after the next browser Frame is produced.
 */
function runAfterFramePaint(callback) {
    // Queue a "before Render Steps" callback via requestAnimationFrame.
    requestAnimationFrame(() => {
        const messageChannel = new MessageChannel();

        // Setup the callback to run in a Task
        messageChannel.port1.onmessage = callback;

        // Queue the Task on the Task Queue
        messageChannel.port2.postMessage(undefined);
    });
}

请使用以下方式:
elem.style.backgroundColor = 'red';
runAfterFramePaint(() => {
    elem.style.backgroundColor = 'blue';
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接