回调中未触发回流/布局?
Posted
技术标签:
【中文标题】回调中未触发回流/布局?【英文标题】:Reflow/layout not triggered in callback? 【发布时间】:2018-09-22 22:05:24 【问题描述】:基于这个问题:How do I know the IntersectionObserver scroll direction?
我试图在可观察回调中重现一些布局/重排案例,但我做不到,所以我尝试简化用例并最终提出这个问题。
我正在阅读 Paul Irish what-forces-layout.md 的要点,我的问题很简单。
在 body 元素上没有回调的输入肯定会触发布局,请参见下面的示例:
element.focus() triggers layout
<!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
但如果将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
这就是我的问题,为什么不触发布局/重排?
【问题讨论】:
这种问题只有构建开发工具的人才可以肯定地回答,例如,可能是开发工具没有注册短路回流,因为布局没有'不改变,因此回流无关。 @Kaiido,有道理,跨浏览器也很难重现 【参考方案1】:我不确定我的问题是否正确,但在情况 1 中,当解析器开始执行脚本时,DOMContentLoaded 尚未触发,它仍在解析文档的其余部分。同时你在elemB
上调用focus
,你会立即触发布局流程。
在情况 2 中,除非您单击文档本身,否则根本不会调用 onClick
函数。您可以通过打开您提供的小提琴上的 “Paint Flashing” 来验证这一点。只有当您单击时,输入才会变为绿色。
而在第一种情况下,您会在开始时看到输入的短暂闪烁(即您对 .focus 的调用),然后是整个 documentElement(在 DOMContentLoaded 处)。
如果两种情况,您只有整个 documentElement 闪烁一次(在 DOMContentLoaded 上,前提是没有其他任何东西触发 reflow/repaint onload 事件),然后每次点击只有一次输入元素。
PS:
据我所知,我已经在本地机器上尝试了您的 2 个案例,有趣的是,在您的第一个案例中,我在 DOMContentLoaded 之后看到 2 个布局 活动。
但是,如果我从您的案例 1 中注释掉 elementB.focus();
行并再次记录,我会再次看到 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 0ms,您将再次获得 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 将触发重绘 + 更新布局树,但不能保证布局垃圾(至少从这些示例中)【讨论】:
这不是我说的意思。当您不调用函数时,不会触发回流,这就是它的全部内容。如果你在任何地方做 elem.scrollTop 它将触发回流,但 function()elem.scrollTop .. 直到它被调用才会触发。这就是包装它们不会触发回流的原因。 问题不是换行,问题是当我点击文档时,reflow
应该在第二个脚本中触发,但它没有被触发,所以我的问题是为什么会这样?在这两种情况下,elementB.focus()
都被调用
如果我打开油漆闪烁并单击文档,我可以清楚地看到回流
repaint
和reflow
不一样你看到的油漆闪烁的是repaint
是的,没错,我会用 PS 更新,我认为你有误报。【参考方案2】:
这真的很难确定为什么开发工具在这种情况下没有注意到回流,这可能是因为由于布局没有变化,回流算法短路并且开发-工具省略了它。也可能是别的东西……
但回流会发生,至少在必要的情况下是这样。
要测试重排,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>
【讨论】:
以上是关于回调中未触发回流/布局?的主要内容,如果未能解决你的问题,请参考以下文章