4

基于这个问题我如何知道 IntersectionObserver 滚动方向?

我试图在可观察的回调中重现一些布局/重排案例,但我做不到,所以我尝试简化用例并最终提出这个问题。

我正在阅读 Paul Irish what-forces-layout.md的要点,我的问题很简单。

在 body 元素上输入没有回调的情况肯定会触发布局,请参见下面的示例:

element.focus() 触发布局

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    elementB.focus();
  </script>
</body>
</html>

查看 chrome 性能记录

但如果将focus事件包装在点击回调中,则不会触发布局/重排。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    function onClick() {
      elementB.focus();
    }

    document.addEventListener('click', onClick);
  </script>
</body>
</html>

查看 chrome 性能记录

所以这就是我的问题,为什么不触发布局/回流?

4

2 回答 2

3

我不确定我的问题是否正确,但在情况 1 中,当解析器开始执行脚本时,DOMContentLoaded 尚未触发,它仍在解析文档的其余部分。同时您调用focuselemB您将立即触发布局流程。

在情况 2 中,onClick除非您单击文档本身,否则根本不会调用函数。您可以通过打开您提供的小提琴上的“Paint Flashing”来验证这一点。只有当您单击时,输入才会变为绿色。

而在第一种情况下,您会在开始时看到输入的短暂闪烁(即您对.focus的调用),然后是整个 documentElement(在 DOMContentLoaded 处)。

如果有两种情况,您只有整个 documentElement 闪烁一次(在 DOMContentLoaded 上,前提是没有其他任何东西触发重排/重绘 onload 事件),然后每次点击只有一次输入元素。

PS:

现在据我所知,我已经在本地计算机上尝试了您的 2 个案例,有趣的是,在您的第一个案例中,我在 DOMContentLoaded 之后立即看到了2 个布局活动。

elementB.focus();但是,如果我从您的案例 1中注释掉该行并再次记录,我会再次看到2 个布局活动

据我了解,浏览器将在启动时执行 2 次布局操作,一旦开始解析主体,然后围绕 DOMContentLoaded。如果任何同步强制布局垃圾是由 javascript 完成的(通过调用链接中列出的任何方法/属性),浏览器将尝试批处理这些操作。

为了测试这种行为,我修改了您的第一种情况,如下所示:

!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text" style="position:relative;top:0px;">
  <script type="text/javascript">
    var elementB = document.querySelector('input');
    elementB.focus();
  </script>
  <script async="true">
    setTimeout(function(){
        elementB.style.top = parseInt(elementB.style.top) + 5 + "px";
    },500)
  </script>
</body>
</html>

现在将会发生的是,在加载事件之后(约 500 毫秒)您将​​有第三个布局活动(异步是不必要的)。但是如果你将 setTİmeout 设置为 0 毫秒你将再次获得 2 个布局活动!(如果您看到 3 个布局,则可能无法保证微任务队列的行为,以强制同步布局,删除第二个脚本标签内的 async 属性和 setTimeout 包装)。底线:所以浏览器对它进行批处理,或者至少这是我从这个例子中看到的。

对于您的第二种情况,当我按照您发布的方式记录它时,我没有看到布局活动(与以前一样的 2 布局)是正确的。但是我看到的是一致的样式重新计算+更新布局树+每次事件后的绘画。这让我认为,一旦布局树更新,如果不需要布局垃圾,则不会重新计算。为了测试这种行为,我更改了您的第二个脚本,如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text" style="position:relative;top:0px;">
  <script type="text/javascript">
    var elementB = document.querySelector('input');

    function onClick() {
      elementB.focus();
      elementB.style.top = parseInt(elementB.style.top) + 5 + "px";
    }

    document.addEventListener('click', onClick);
  </script>
</body>
</html>

这里每点击一次文档,输入框就会下移5个像素。如果您在 10 秒内记录多个点击事件,您将看到很多更新布局树 + 重绘和布局垃圾。这让我认为只有在必要时才在更新布局树后完成布局垃圾。

结论(我可能大错特错)

  • 浏览器将在解析 HTML 期间尝试批处理布局活动。
  • element.focus 将触发重绘 + 更新布局树,但不能保证布局垃圾(至少从这些示例中)
于 2018-04-23T14:53:57.683 回答
1

这真的很难确定为什么开发工具在这种情况下没有注意到回流,这可能是因为由于布局没有变化,回流算法被短路并且开发工具省略了它. 也可能是别的东西……

但是回流会发生,至少如果它必须这样做的话。

要测试回流,IMO 最好的方法是触发需要它的东西,CSS 过渡就是这样。

var input = document.querySelector('input');
// test our logic without anything that should trigger a reflow 
noreflow.onclick = function() {
  // from 20px in CSS
  test.style.transition = 'none';
  test.style.width = '100px'; // should be ignored
  test.style.transition = 'width 1s linear';
  test.style.width = '20px';
  // should go from 20px to 20px => no transition
};
// test with manually triggered reflow
reflow.onclick = function() {
  // from 20px in CSS
  test.style.transition = 'none';
  test.style.width = '100px';
  input.focus(); // reflow
  // now should be computed as 100px
  test.style.transition = 'width 1s linear';
  test.style.width = '20px';
  // now that should move
}
#test {
  width: 20px;
  height: 20px;
  background: red;
}
<input type="text"><br>
<button id="noreflow">no reflow</button>
<button id="reflow">reflow</button>
<div id="test"></div>

于 2018-04-25T11:55:07.367 回答