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

How does really work rendering in browser (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)";
});
.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 视频中的操作。但不是 chrome 的情况。似乎 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 和 chrome 在演示 2 上做的一样。他们已经改变了他们的地方 我还测试了事件: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>

这里我们看到 firefox 的工作方式和 chrome 一样好,这是 Archibald 所说的。但是你还记得演示 2,两个版本,为什么它们的行为如此不同?

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


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

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

那么 Chrome 中发生了什么?我目前在 phone 上,无法进行广泛的测试,但我已经可以看出罪魁祸首是设置过渡的行,首先设置它会“解决”问题。

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 .