Seitenrendering-Prozess
Bevor wir das Neuzeichnen und Umfließen von Seiten besprechen. Sie müssen ein gewisses Verständnis für den Seitenrenderingprozess haben, wie die Seite HTML in Kombination mit CSS usw. im Browser anzeigt. Das folgende Flussdiagramm zeigt den Verarbeitungsablauf des Browsers für die Seitenrendering. Verschiedene Browser können leicht unterschiedlich sein. Aber im Grunde sind sie ähnlich.
1. Der Browser analysiert den erhaltenen HTML-Code in einen Dom-Baum, und der Stammknoten ist unser häufig verwendetes Dokumentobjekt. html>-Tag). Der Dom-Baum ist die HTML-Struktur, die wir mit Tools wie Firebug oder der IE Developer Toolbar sehen. Er enthält alle HTML-Tags, einschließlich display:none versteckt, und mit JS dynamisch hinzugefügte Elemente.
2. Der Browser analysiert alle Stile (hauptsächlich einschließlich CSS und Browser-Stileinstellungen). Während des Analysevorgangs werden Stile entfernt, die der Browser nicht erkennen kann style am Anfang und Firefox entfernt den Style am Anfang von _.
3. Der Dom-Baum und die Stilstruktur werden kombiniert, um einen Render-Baum zu bilden. Der Render-Baum ähnelt in gewisser Weise dem Dom-Baum, aber tatsächlich gibt es einen großen Unterschied. Jeder Renderbaum hat seinen eigenen Stil und der Renderbaum enthält keine versteckten Knoten (z. B. display:none-Knoten und Kopfknoten), da diese Knoten nicht zum Rendern verwendet werden und sich nicht auf das Rendern auswirken in den Renderbaum aufgenommen werden. Beachten Sie, dass durch Visibility:hidden ausgeblendete Elemente weiterhin in den Renderbaum aufgenommen werden, da sich Visibility:hidden auf das Layout auswirkt und Platz einnimmt. Gemäß den Standards von CSS2 wird jeder Knoten im Renderbaum als Box (Boxabmessungen) bezeichnet, und alle Attribute der Box sind: Breite, Höhe, Rand, Abstand, links, oben, Rand usw.
4. Sobald der Renderbaum erstellt ist, kann der Browser die Seite basierend auf dem Renderbaum zeichnen.
Reflow und Neuzeichnen
1. Wenn ein Teil (oder der gesamte) des Renderbaums aufgrund von Änderungen in der Größe, dem Layout, dem Ausblenden usw. der Elemente neu erstellt werden muss. Dies nennt man Reflow (eigentlich denke ich, dass es einfacher und klarer ist, es Neuanordnung zu nennen). Jede Seite muss mindestens einmal umgebrochen werden, und zwar dann, wenn die Seite zum ersten Mal geladen wird.
2. Wenn einige Elemente im Renderbaum aktualisiert werden müssen, wirken sich diese Attribute nur auf das Erscheinungsbild und den Stil der Elemente aus, haben jedoch keinen Einfluss auf das Layout, z. B. die Hintergrundfarbe. Man nennt es Neuzeichnen.
Hinweis: Wie aus dem Obigen hervorgeht, führt ein Reflow definitiv zu einem Neuzeichnen, aber ein Neuzeichnen führt nicht unbedingt zu einem Reflow.
Welche Vorgänge führen zu einem Neuzeichnen und Neuzeichnen?
Tatsächlich führt jeder Vorgang an den Elementen im Renderbaum zu einem Neuzeichnen oder Neuzeichnen, wie zum Beispiel:
1 (Umfließen + Neuzeichnen)
2. Elemente ausblenden, anzeigen: keine (Umfließen + Neuzeichnen), Sichtbarkeit: ausgeblendet (nur Neuzeichnen, kein Umfließen)
3. links (die Animationsmethode von jquery besagt, dass sich das Ändern von oben und links nicht unbedingt auf den Reflow auswirkt) oder das Element in ein anderes übergeordnetes Element verschieben. (Neu zeichnen + umfließen)
4. Operationen am Stil (verschiedene Attributoperationen haben unterschiedliche Auswirkungen)
5. Es gibt auch eine Benutzeroperation, wie z. B. das Ändern der Browsergröße und der Browserschriftart Größe usw. (Reflow + Neuzeichnen)
Sehen wir uns an, wie sich der folgende Code auf Reflow und Neuzeichnen auswirkt:
var s = document.body.style; s.padding = "2px"; // 回流+重绘 s.border = "1px solid red"; // 再一次 回流+重绘 s.color = "blue"; // 再一次重绘 s.backgroundColor = "#ccc"; // 再一次 重绘 s.fontSize = "14px"; // 再一次 回流+重绘 // 添加node,再一次 回流+重绘 document.body.appendChild(document.createTextNode('abc!'));
Bitte beachten Sie, dass ich wieder „Wie viele“ verwendet habe.
Apropos, jeder weiß, dass Reflow teurer ist als Neuzeichnen. Die Kosten für Reflow hängen davon ab, wie viele Knoten im Renderbaum neu erstellt werden müssen, beispielsweise durch Einfügen 1 an der Vorderseite des Körpers führt zu einem Reflow des gesamten Renderbaums, was natürlich teurer ist. Wenn jedoch ein Element nach dem Körper eingefügt wird, hat dies keinen Einfluss auf den Reflow der vorherigen Elemente.
聪明的浏览器
从上个实例代码中可以看到几行简单的JS代码就引起了6次左右的回流、重绘。而且我们也知道回流的花销也不小,如果每句JS操作都去回流重绘的话,浏览器可能就会受不了。所以很多浏览器都会优化这些操作,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会把flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。
虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些style信息的时候,就会让浏览器flush队列,比如:
1. offsetTop, offsetLeft, offsetWidth, offsetHeight
2. scrollTop/Left/Width/Height
3. clientTop/Left/Width/Height
4. width,height
5. 请求了getComputedStyle(), 或者 ie的 currentStyle
当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。
如何减少回流、重绘
减少回流、重绘其实就是需要减少对render tree的操作,并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:
1. 不要1个1个改变元素的样式属性,最好直接改变className,但className是预先定义好的样式,不是动态的,如果你要动态改变一些样式,则使用cssText来改变,见下面代码:
// 不好的写法 var left = 1; var top = 1; el.style.left = left + "px"; el.style.top = top + "px"; // 比较好的写法 el.className += " className1"; // 比较好的写法 el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
2. 让要操作的元素进行"离线处理",处理完后一起更新,这里所谓的"离线处理"即让元素不存在于render tree中,比如:
a) 使用documentFragment或div等元素进行缓存操作,这个主要用于添加元素的时候,大家应该都用过,就是先把所有要添加到元素添加到1个div(这个div也是新加的),
最后才把这个div append到body中。
b) 先display:none 隐藏元素,然后对该元素进行所有的操作,最后再显示该元素。因对display:none的元素进行操作不会引起回流、重绘。所以只要操作只会有2次回流。
3 不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,就先读取到变量中进行缓存,以后用的时候直接读取变量就可以了,见下面代码:
// 别这样写,大哥 for(循环) { el.style.left = el.offsetLeft + 5 + "px"; el.style.top = el.offsetTop + 5 + "px"; } // 这样写好点 var left = el.offsetLeft,top = el.offsetTop,s = el.style; for(循环) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
4. 考虑你的操作会影响到render tree中的多少节点以及影响的方式,影响越多,花费肯定就越多。比如现在很多人使用jquery的animate方法移动元素来展示一些动画效果,想想下面2种移动的方法:
// block1是position:absolute 定位的元素,它移动会影响到它父元素下的所有子元素。
// 因为在它移动过程中,所有子元素需要判断block1的z-index是否在自己的上面,
// 如果是在自己的上面,则需要重绘,这里不会引起回流
$("#block1").animate({left:50});
// block2是相对定位的元素,这个影响的元素与block1一样,但是因为block2非绝对定位
// 而且改变的是marginLeft属性,所以这里每次改变不但会影响重绘,
// 还会引起父元素及其下元素的回流
$("#block2").animate({marginLeft:50});
实例测试
最后用2个工具对上面的理论进行一些测试,这2个工具是在我 "web 性能测试工具推荐" 文章中推荐过的工具,分别是:dynaTrace(测试ie),Speed Tracer(测试Chrome)。
第一个测试代码不改变元素的规则,大小,位置。只改变颜色,所以不存在回流,仅测试重绘,代码如下:
<body> <script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red';; tmp = computed.backgroundColor; s.color = 'white'; tmp = computed.backgroundImage; s.color = 'green'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.color = 'pink'; s.color = 'blue'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button> </body>
testOneByOne 函数改变3次color,其中每次改变后调用getComputedStyle,读取属性值(按我们上面的讨论,这里会引起队列的flush),testAll 同样是改变3次color,但是每次改变后并不马上调用getComputedStyle。
我们先点击Test One by One按钮,然后点击 Test All,用dynaTrace监控如下:
上图可以看到我们执行了2次button的click事件,每次click后都跟一次rendering(页面重绘),2次click函数执行的时间都差不多,0.25ms,0.26ms,但其后的rendering时间就相差一倍多。(这里也可以看出,其实很多时候前端的性能瓶颈并不在于JS的执行,而是在于页面的呈现,这种情况在用JS做到富客户端中更为突出)。我们再看图的下面部分,这是第一次rendering的详细信息,可以看到里面有2行是 Scheduleing layout task,这个就是我们前面讨论过的浏览器优化过的队列,可以看出我们引发2次的flush。
再看第二次rendering的详细信息,可以看出并没有Scheduleing layout task,所以这次rendering的时间也比较短。
测试代码2:这个测试跟第一次测试的代码很类似,但加上了对layout的改变,为的是测试回流。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> </head> <body> <script type="text/javascript"> var s = document.body.style; var computed; if (document.body.currentStyle) { computed = document.body.currentStyle; } else { computed = document.defaultView.getComputedStyle(document.body, ''); } function testOneByOne(){ s.color = 'red'; s.padding = '1px'; tmp = computed.backgroundColor; s.color = 'white'; s.padding = '2px'; tmp = computed.backgroundImage; s.color = 'green'; s.padding = '3px'; tmp = computed.backgroundAttachment; } function testAll() { s.color = 'yellow'; s.padding = '4px'; s.color = 'pink'; s.padding = '5px'; s.color = 'blue'; s.padding = '6px'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment; } </script> color test <br /> <button onclick="testOneByOne()">Test One by One</button> <button onclick="testAll()">Test All</button> </body>
用dynaTrace监控如下:
相信这图不用多说大家都能看懂了吧,可以看出有了回流后,rendering的时间相比之前的只重绘,时间翻了3倍了,可见回流的高成本性啊。
大家看到时候注意明细处相比之前的多了个 Calcalating flow layout。
最后再使用Speed Tracer测试一下,其实结果是一样的,只是让大家了解下2个测试工具:
测试1:
图上第一次点击执行2ms(其中有50% 用于style Recalculation), 第二次1ms,而且第一次click后面也跟了2次style Recalculation,而第二次点击却没有style Recalculation。
但是这次测试发现paint重绘的时间竟然是一样的,都是3ms,这可能就是chrome比IE强的地方吧。
测试2:
从图中竟然发现第二次的测试结果在时间上跟第一次的完全一样,这可能是因为操作太少,而chrome又比较强大,所以没能测试明显结果出来,
但注意图中多了1个紫色部分,就是layout的部分。也就是我们说的回流。
以上就是高性能WEB开发 页面呈现、重绘、回流。的内容,更多相关文章请关注PHP中文网(www.php.cn)!