在浏览器中渲染是如何真正工作的(事件循环)

Max*_*Pro 7 javascript firefox google-chrome event-loop

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

应该说我们得用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)";
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)

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

演示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)";
    });
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)

在 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)";
    });
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)

现在我们看到 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)";
      });
    });
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)

在这里我们看到 Firefox 和 chrome 一样工作,正如阿奇博尔德所说的那样。但是你还记得 demo 2,两个版本,为什么它们的行为如此不同吗?

Kai*_*ido 3

TL;博士; 如果您希望代码在任何地方都一样工作,请在将所需的值设置为初始值后自行强制回流。

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)";
    });
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)


您在这里遇到的事情称为回流。我已经在其他 答案中写过它,但基本上这个回流是计算页面中确定如何绘制每个元素所需的所有框。
这种回流(又名布局或重新计算)可能是一项昂贵的操作,因此浏览器在执行此操作之前会尽可能等待。
然而,何时发生这种情况并不属于事件循环规范的一部分。唯一的限制是,当ResizeObserver 的通知被触发时,重新计算已经完成。
虽然实现可以很好地在他们愿意之前完成它(例如 Safari 会在它有一个小的空闲时间时立即完成它),或者我们甚至可以通过访问一些确实需要更新布局的属性来强制它。

因此,在重新计算此布局之前,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)";
    });
});
Run Code Online (Sandbox Code Playgroud)
.main {
  width: 100px;
  height: 100px;
  background: orange;
  }
Run Code Online (Sandbox Code Playgroud)
<div id="block" class="main"></div>
Run Code Online (Sandbox Code Playgroud)

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

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