【问题标题】:How does really work rendering in browser (event loop)如何在浏览器中真正进行渲染(事件循环)
【发布时间】:2021-11-03 05:35:15
【问题描述】:

我已经创建了简单的演示,让我们开始吧...

应该说一下我们有什么用chrome和firefox对比一下

演示 1:

block.addEventListener("click", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "";
    block.style.transition = "transform 1s ease-in-out";
    block.style.transform = "translateX(100px)";
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

在两种浏览器中,我们都看不到任何变化

演示 2:

block.addEventListener("click", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "";
    requestAnimationFrame(() => {
      block.style.transition = "transform 1s ease-in-out";
      block.style.transform = "translateX(100px)";
    });
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

在 chrome 中我们会看到动画,在 Firefox 中我们会看到另一件事。需要提到的是,Firefox 符合 Jake Archibald in the Loop 视频中的操作。但不是在铬的情况下。似乎firefox符合规范,但不符合chrome

演示 2(替代):

block.addEventListener("mouseover", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "";
    requestAnimationFrame(() => {
      block.style.transition = "transform 1s ease-in-out";
      block.style.transform = "translateX(100px)";
    });
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

现在我们看到 chrome 可以正常工作,但是 firefox 和 Demo 2 中的 chrome 做的一样。他们已经改变了他们的位置 我还测试了事件:mouseenter、mouseout、mouseover、mouseleave、mouseup、mousedown。最有趣的是最后两个在 chrome 和 firefox 中的工作方式相同,我认为它们都是不正确的。

总结:这两个 UA 对事件的处理方式似乎不同。但是他们是怎么做的呢?

演示 3:

block.addEventListener("click", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "";
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        block.style.transition = "transform 1s ease-in-out";
        block.style.transform = "translateX(100px)";
      });
    });
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

通过 Archibald 的话,在这里我们看到 firefox 和 chrome 一样好用,这是预期的。但是你还记得演示 2,两个版本,为什么它们的行为如此不同?

【问题讨论】:

  • 我离开键盘几天了,所以我还不能给出答案,但请注意,当回流发生时,你的两个第一个 sn-ps 中没有任何东西,只有第三个一个是肯定的。如果您想要始终转换到工作状态,请强制进行回流,如下所示:stackoverflow.com/questions/55134528/… 如果您对两种实现中发生的情况感兴趣,请等几天,我会写这篇文章。
  • @Kaiido 我对你完整的答案很好奇,我会等的。你对我的回答太好了,我很高兴:)
  • @Kaiido 在你完成你的答案之前,我想问一件事(我想自己阅读)。当我在 chrome 浏览器的开发工具中检查性能选项卡时,我注意到我看到了下一个动作链:动画帧触发 => 重新计算样式 => 更新层树 => 绘制 => 复合层。而且我已经阅读了一些关于事件循环的规范,但我没有看到有关重新计算样式和使用合成更新图层树的步骤。都在哪里?
  • 这不是规范的一部分,实施者可以随心所欲。在我链接到的答案中,实际上已经解释了这部分的链接,请参阅stackoverflow.com/questions/47342730/…
  • @Kaiido 那么应该如何理解这些步骤放在哪里呢?嗯,我已经阅读了您的链接,但我仍然正确理解步骤11. Update the rendering: if this is a window event loop, then: 和子步骤16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state 中的内容,这里 16 步的意思是:recalculate styles => update layer tree => paint => composite layers...我正确吗? html.spec.whatwg.org/multipage/…

标签: javascript google-chrome firefox event-loop


【解决方案1】:

TL;DR;如果您希望您的代码在您将所需的值设置为初始值后在任何地方都可以使用force a reflow

block.addEventListener("click", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "";
    requestAnimationFrame(() => {
      // if you want your transition to start from 0
      block.style.transform = "translateX(0px)";
      // force reflow
      document.body.offsetWidth;
      block.style.transition = "transform 1s ease-in-out";
      block.style.transform = "translateX(100px)";
    });
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

您在这里遇到的问题称为回流。我已经在otheranswers 中写过它,但基本上这种重排是计算页面中所有需要确定如何绘制每个元素的框。
这种重排(也称为布局或重新计算)可能是一项昂贵的操作,因此浏览器会在执行此操作之前尽可能多地等待。
但是,何时发生这种情况不是事件循环规范的一部分。唯一的限制是当ResizeObserver's notifications are to be fired 重新计算完成时。
虽然如果他们愿意的话,实现之前可以很好地做到这一点(例如,Safari 会在它有一小段空闲时间时做到这一点),或者我们甚至可以通过访问确实需要更新布局的 some properties 来强制它。

因此,在重新计算此布局之前,CSSOM 甚至不会看到您传递给元素样式的新值,它会像您同步更改这些值一样对待它,即它会忽略所有以前的值。
鉴于 Firefox 和 Chrome 都等到最后一刻(在触发 ResizeObserver 的通知之前)触发重排,我们确实可以预期在这些浏览器中,您的转换将从初始位置 (translate(0)) 开始,并且中间值将被忽略。但再一次,这对 Safari 来说并非如此。

那么在 Chrome 中会发生什么?我目前正在使用手机,无法进行广泛的测试,但我已经可以看到罪魁祸首是设置转换的线路,首先设置它会“解决”问题。

block.addEventListener("click", () => {
    block.style.transform = "translateX(500px)";
    block.style.transition = "transform 1s ease-in-out";
    requestAnimationFrame(() => {
      block.style.transform = "translateX(100px)";
    });
});
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
<div id="block" class="main"></div>

由于我必须做出猜测,我会说设置过渡可能会使元素切换其渲染路径(例如,从 CPU 渲染到 GPU 渲染)并且它们将强制回流这样做。但这仍然只是一个没有适当测试的猜测。最好向https://crbug.com 提出问题,因为这可能不是预期的行为。

至于为什么你对不同的事件有不同的行为,这可能是因为这些事件在不同的时刻触发,例如至少 mousemove 会被限制为绘制帧,但我必须仔细检查 mousedown 和 mouseup。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-03
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    • 1970-01-01
    • 2011-07-22
    • 2013-10-11
    • 1970-01-01
    相关资源
    最近更新 更多