简体   繁体   English

回调/布局没有在回调中触发?

[英]Reflow/layout not triggered in callback?

Based on this question : How do I know the IntersectionObserver scroll direction? 基于这个问题我如何知道IntersectionObserver滚动方向?

I was trying to reproduce some layout/reflow cases within the observable callback, but I couldn't, so I tried to simplify the use case and end up asking this question. 我试图在可观察的回调中重现一些布局/重排案例,但我不能,所以我试图简化用例并最终提出这个问题。

I was reading the gist of Paul Irish what-forces-layout.md and my question is very simple. 我正在阅读Paul Irish what-forces-layout.md的要点,我的问题非常简单。

Having the case of an input without a callback on the body element definitely triggers layout, see the example below: 如果在body元素上没有回调的输入情况肯定会触发布局,请参阅下面的示例:

element.focus() triggers layout 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> 

see chrome performance record 看铬表现记录

But if wrap the focus event in a click callback, doesn't trigger layout/reflow. 但是如果将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> 

see chrome performance record 看铬表现记录

So that's my question why isn't triggering layout/reflow? 所以我的问题是为什么不触发布局/回流?

I am not sure If I get your question right but in case 1, when the parser is starts executing the script, the DOMContentLoaded is not fired yet and it is still parsing rest of the document. 我不确定如果我的问题是正确的,但在案例1中,当解析器开始执行脚本时,DOMContentLoaded尚未被解雇,它仍在解析文档的其余部分。 Meanwhile you call focus on the elemB , you are immediately triggering the layout flow. 同时你将focus放在elemB ,你会立即触发布局流程。

In case 2, onClick function is not called at all unless you click the document itself. 在案例2中,除非单击文档本身,否则根本不会调用onClick函数。 You can verify this by turning on the "Paint Flashing" on the fiddle you provided. 您可以通过打开您提供的小提琴上的“Paint Flashing”来验证这一点。 The input will become green only when you click. 只有单击时输入才会变为绿色。

Whereas in the first case you see a brief flash of the input at start (thats your call to .focus ) and then the whole documentElement (at DOMContentLoaded). 而在第一种情况下,您会在开始时看到输入的短暂闪烁(这是您对.focus的调用),然后是整个documentElement(在DOMContentLoaded处)。

In case two you only have the whole documentElement flash once (on DOMContentLoaded, provided that nothing else trigger reflow/repaint onload event) and then only the input element once per click. 如果是两个你只有整个documentElement flash一次(在DOMContentLoaded上,只要没有别的东西触发reflow / repaint onload事件),然后每次点击只有一次输入元素。

PS: PS:

Now as far as I can see, I have tried your 2 cases on my local machine and interestingly in your first case I see 2 layout activites right after DOMContentLoaded. 现在据我所知,我已经在我的本地机器上尝试了2个案例,有趣的是在第一个案例中,我在DOMContentLoaded之后看到了2个布局激活。

However if I comment out the line elementB.focus(); 但是,如果我注释掉elementB.focus(); from your case 1 and record again, I see 2 layout activities again . 从你的案例1再次记录,我再次看到2个布局活动

From my understanding the browser will do 2 layout operations on start, once it starts to parse the body and then once around DOMContentLoaded. 根据我的理解,浏览器将在启动时执行2个布局操作,一旦它开始解析主体,然后在DOMContentLoaded周围执行一次。 And if any synchronous forced layout trashing is done by javascript (by calling any of the methods/properties listed in your link), the browser will try to batch these operations. 如果通过javascript完成任何同步强制布局垃圾 (通过调用链接中列出的任何方法/属性),浏览器将尝试批处理这些操作。

To test this behavior, I modified your 1st case as below: 为了测试这种行为,我修改了你的第一个案例如下:

!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>

Now what will happen is, you will have a third layout activity right after (~500ms) the load event (async is unnecessary). 现在将会发生的是,您将在加载事件~500ms)之后立即进行第三次布局活动 (不需要异步)。 But if you were to make the setTİmeout 0ms , you would get 2 layout activities again ! 但是如果你要使setTİmeout为0ms ,你将再次获得2个布局活动 (the microtask queue behavior might not be guaranteed, in case you see 3 layouts, to force sync layout, remove the async attribute and the setTimeout wrap inside the 2nd script tag). (如果您看到3个布局,则可能无法保证微任务队列行为,强制同步布局,删除第二个脚本标记内的异步属性和setTimeout换行)。 Bottom Line: So the browser batches it, or at least this is what I see from this example. 底线:因此浏览器对其进行批处理,或者至少这是我在此示例中看到的内容。

For your second case, when I record it the way you posted, it is correct that I do not see layout activity (2 layout as before). 对于你的第二种情况,当我以你发布的方式记录它时,我没有看到布局活动(2布局和以前一样)是正确的。 But what I see is a consistent style recalculation + update layout tree + painting after each event. 但我看到的是每个事件后一致的样式重新计算+更新布局树+绘画 This makes me think that once the layout tree is updated, if layout trashing is not necessary it is not recalculated. 这让我觉得一旦布局树更新,如果不需要布局垃圾,就不会重新计算。 To test that behavior, I changed your second script as below: 为了测试这种行为,我改变了你的第二个脚本,如下所示:

<!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>

Here each time you click the document, the input box will move down 5 pixels. 每次单击文档时,输入框将向下移动5个像素。 If you record during 10 seconds for multiple click events you will see a lot of update layout tree + repaint AND layout trashing as well. 如果您在10秒内记录多次点击事件,您将看到很多更新布局树+重绘和布局垃圾 This makes me think layout trashing is done after updating layout tree if only necessary. 这让我觉得如果只是必要的话,在更新布局树之后就会完成布局垃圾。

CONCLUSION (I might be terribly wrong) 结论(我可能非常错误)

  • The browser will try to batch layout activity during parsing of the HTML. 浏览器将尝试在解析HTML期间批量处理布局活动。
  • element.focus will trigger repaint + update layout tree, but layout trashing is not guaranteed (at least from these examples) element.focus将触发重绘+更新布局树,但不保证布局垃圾(至少从这些示例中)

That' really hard to tell for sure why the dev tools don't notice the reflow in this case, it might be because since there has been no changes in the layout, the reflow algorithm got short-circuited and the dev-tools omitted it. 在这种情况下,确实很难确定为什么开发工具没有注意到回流,这可能是因为由于布局没有变化,回流算法被短路并且开发工具省略了它。 It could also be something else... 它也可能是别的......

But the reflow happens, at least if it has to. 但是回流发生了,至少如果必须的话。

To test reflow, the best way IMO is to trigger something that needs it, and CSS transitions are such a thing. 要测试reflow,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> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM