在介紹瀏覽器的重排與重繪之前亮航,先了解一下瀏覽器的工作原理一旦我們了解了瀏覽器是如何工作的,我們就可以更好的去駕馭它届垫。
現(xiàn)代瀏覽器通常擁有兩個(gè)重要的執(zhí)行線程驮肉,這兩個(gè)線程相互配合來渲染出頁面:
主線程
通常情況下,主線程主要負(fù)責(zé)以下工作:運(yùn)行JavaScript四苇、計(jì)算HTML元素的CSS樣式孝凌、布局頁面、把頁面元素繪制成一個(gè)或多個(gè)位圖月腋、把這些位圖移交給排版線程
排版線程
通常情況下蟀架,排版線程主要負(fù)責(zé)以下工作:通過GPU渲染位圖,并顯示在屏幕上榆骚、向主線程請(qǐng)求更新位圖的可見部分或即將可見的部分片拍、判斷出當(dāng)前頁面處于可見的部分、判斷出即將通過頁面滾動(dòng)而可見的部分妓肢、隨著用戶滾動(dòng)頁面來移動(dòng)這些部分(可見部分的和即將可見的部分)
GPU
排版線程通過GPU把位圖繪制到了屏幕上穆碎。
GPU比較擅長于:繪制位圖到屏幕、重復(fù)的繪制同一個(gè)位圖职恳、在不同的位置所禀,以不同的旋轉(zhuǎn)角度,或者不同的縮放大小來繪制同一個(gè)位圖放钦。
GPU相對(duì)慢的地方:將位圖加載到顯存里色徘。
重排與重繪
瀏覽器下載完頁面中的所有組件——HTML標(biāo)記、JavaScript操禀、CSS褂策、圖片之后會(huì)解析生成兩個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)——DOM樹和渲染樹。
DOM樹表示頁面結(jié)構(gòu),渲染樹表示DOM節(jié)點(diǎn)如何顯示斤寂。DOM樹中的每一個(gè)需要顯示的節(jié)點(diǎn)在渲染樹種至少存在一個(gè)對(duì)應(yīng)的節(jié)點(diǎn)(隱藏的DOM元素 disply值為none 在渲染樹中沒有對(duì)應(yīng)的節(jié)點(diǎn))耿焊。渲染樹中的節(jié)點(diǎn)被稱為“幀”或“盒”,符合CSS模型的定義,理解頁面元素為一個(gè)具有填充遍搞,邊距罗侯,邊框和位置的盒子。一旦 DOM和渲染樹構(gòu)建完成溪猿,瀏覽器就開始顯示(繪制)頁面元素钩杰。
當(dāng)DOM的變化影響了元素的幾何屬性(寬或高),瀏覽器需要重新計(jì)算元素的幾何屬性诊县,同樣其他元素的幾何屬性和位置也會(huì)因此受到影響讲弄。瀏覽器會(huì)使渲染樹中受到影響的部分失效,并重新構(gòu)造渲染樹依痊。這個(gè)過程稱為重排避除。完成重排后,瀏覽器會(huì)重新繪制受影響的部分到屏幕胸嘁,該過程稱為重繪驹饺。
tips:并不是所有的DOM變化都會(huì)影響幾何屬性,比如改變一個(gè)元素的背景色并不會(huì)影響元素的寬和高缴渊,這種情況下只會(huì)發(fā)生重繪赏壹。
引起重排的情況
很顯然,每次重排衔沼,必然會(huì)導(dǎo)致重繪蝌借,那么,重排會(huì)在哪些情況下發(fā)生指蚁?
- 添加或者刪除可見的DOM元素
- 元素位置改變
- 元素尺寸改變
- 元素內(nèi)容改變(例如:一個(gè)文本被另一個(gè)不同尺寸的圖片替代)
- 頁面渲染初始化(無法避免)
- 瀏覽器窗口尺寸改變
這些都是顯而易見的菩佑,或許你已經(jīng)有過這樣的體會(huì),不間斷地改變?yōu)g覽器窗口大小凝化,導(dǎo)致UI反應(yīng)遲鈍(某些低版本IE下甚至直接掛掉)稍坯,現(xiàn)在你可能恍然大悟,沒錯(cuò)搓劫,正是一次次的重排重繪導(dǎo)致的瞧哟!
transition
了解了重排與重繪之后,現(xiàn)在我們看一下瀏覽器的主線程和排版線程是如何協(xié)同工作來完成一個(gè)CSS Transition的枪向。
假設(shè)我們想要將一個(gè)元素的高度值從100px轉(zhuǎn)換到200px勤揩,如下所示:
div {
height: 100px;
transition: height 1s linear;
}
div:hover {
height: 200px;
}
主線程和排版線程會(huì)根據(jù)下圖所示時(shí)序圖來完成這個(gè)Transition。注意:在橙色方框中的操作是潛在的耗時(shí)操作秘蛔,藍(lán)色方框中的操作是較快的操作陨亡。
正如你所見傍衡,整個(gè)過程有很多橙色的方框,意味著瀏覽器有相當(dāng)繁重的工作要處理负蠕,也意味著這個(gè)Transition可能會(huì)出現(xiàn)卡頓蛙埂。
在整個(gè)Transition的每一幀中,瀏覽器都要去重新布局遮糖,繪制頁面绣的,并把最新的位圖對(duì)象加載到GPU。我們前邊了解過止吁,把位圖對(duì)象加載到GPU的內(nèi)存中是個(gè)相對(duì)緩慢的操作被辑。
瀏覽器之所以要在每一幀動(dòng)畫上處理如此繁重的工作是因?yàn)檫@個(gè)元素的內(nèi)容一直在變化燎悍。修改一個(gè)元素的高度可能會(huì)引起其子元素也要相應(yīng)的改變大小敬惦,因此瀏覽器必須去重新布局。重新布局后谈山,主線程必須為該元素重新生成位圖對(duì)象俄删。
transition: transform
由此可見,對(duì)高度進(jìn)行的Transition相對(duì)來說性能比較差奏路,那有一些性能比較好的Transition嗎畴椰?
假設(shè)我們想要把一個(gè)元素從一半大小縮放到實(shí)際大小,并假設(shè)我們使用CSS的transform 屬性來對(duì)它進(jìn)行縮放鸽粉,同時(shí)使用CSS的transition屬性來生成縮放的動(dòng)畫效果斜脂,如下所示:
div {
transform: scale(0.5);
transition: transform 1s linear;
}
div:hover {
transform: scale(1.0);
}
我們看到只有很少的幾個(gè)橙色的方框,意味著這個(gè)動(dòng)畫效果可能會(huì)很流暢触机!那么帚戳,一個(gè)元素的transform動(dòng)畫效果與其高度的動(dòng)畫效果有什么不同呢?
根據(jù)定義儡首,CSS的transform屬性不會(huì)改變?cè)氐牟季制危膊粫?huì)影響到其周圍的元素。它把元素當(dāng)做一個(gè)整體看待——縮放整個(gè)元素蔬胯、旋轉(zhuǎn)整個(gè)元素或者移動(dòng)整個(gè)元素对供。
這對(duì)瀏覽器來說是一個(gè)好消息!瀏覽器只需在動(dòng)畫開始的時(shí)候生成這個(gè)元素的位圖對(duì)象氛濒,并把它傳遞給GPU产场。在這之后,瀏覽器無需再做任何重新布局舞竿,繪制頁面以及傳遞位圖對(duì)象的操作了涝动,相反,瀏覽器可以利用GPU擅長的繪制的特點(diǎn)來快速的在不同的位置炬灭,旋轉(zhuǎn)或縮放同一個(gè)位圖對(duì)象醋粟。
設(shè)計(jì)決策
那么靡菇,是否這就意味這我們不要去緩動(dòng)一個(gè)元素的高度?非也米愿,一些情況下厦凤,這是你的設(shè)計(jì)效果的一部分,并且動(dòng)畫效果可以非秤叮快的完成较鼓。也許動(dòng)畫的元素是孤立的,不會(huì)引起頁面其他部分進(jìn)行重新布局违柏;也許該元素只是單純的進(jìn)行重繪博烂,瀏覽器可以快速的完成;也許該元素很小漱竖,瀏覽器只需將很小的位圖對(duì)象傳遞給GPU禽篱。
當(dāng)然了,在不影響你設(shè)計(jì)的視覺效果的情況下馍惹,最好去緩動(dòng)一個(gè)性能較好的CSS屬性躺率,如transform,而不是去緩動(dòng)一個(gè)性能較差的CSS屬性万矾,如height悼吱。舉例來說,假設(shè)你的設(shè)計(jì)中有一個(gè)按鈕良狈,當(dāng)點(diǎn)擊它的時(shí)候會(huì)出來一個(gè)菜單后添,試著去緩動(dòng)菜單的transform屬性來顯示它而不是緩動(dòng)它的top或height屬性來達(dá)到類似的效果。
在動(dòng)畫上特別快的CSS屬性包括:
- CSS transform
- CSS opacity
- CSS filter
下面介紹重排重繪造成的性能開銷和如何進(jìn)行優(yōu)化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>css3-transform轉(zhuǎn)換成maxtrix</title>
<style>
.container{
position: relative;
min-height: 400px;
}
.box{
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
border-radius: 50%;
background-color: orange;
}
.animation-run{
animation: move 4s infinite;
}
@keyframes move {
0%{
top: 0;
left: 0;
}
25%{
top: 0;
left: 200px;
}
50%{
top: 200px;
left: 200px;
}
75%{
top: 200px;
left: 0;
}
}
</style>
</head>
<body>
<div class="container">
<div class="box animation-run"></div>
</div>
</body>
</html>
這個(gè)是沒有開啟3d的動(dòng)畫薪丁,下面進(jìn)行錄制20s看看主要消耗的時(shí)間是哪一部分遇西,主要開銷的時(shí)間是剛剛開始rendering的時(shí)間和造成重排的時(shí)間
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>css3-transform轉(zhuǎn)換成maxtrix</title>
<style>
.container{
position: relative;
min-height: 400px;
}
.box{
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
border-radius: 50%;
background-color: orange;
}
.animation-run{
animation: move 4s infinite;
}
/* transform不會(huì)發(fā)生重排重繪 */
@keyframes move {
0%{
transform: translate(0,0);
}
25%{
transform: translate(200px,0);
}
50%{
transform: translate(200px,200px);
}
75%{
transform: translate(0,200px);
}
}
</style>
</head>
<body>
<div class="container">
<div class="box animation-run"></div>
</div>
</body>
</html>
這個(gè)例子是使用transform來做動(dòng)畫,開啟了硬件加速窥突,不會(huì)造成重排重繪努溃,很明顯下圖沒有了paint消耗的時(shí)間
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>css3-transform轉(zhuǎn)換成maxtrix</title>
<style>
.container{
position: relative;
min-height: 400px;
}
.box{
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
border-radius: 50%;
background-color: orange;
}
.animation-run{
animation: move 4s infinite;
}
/* 用矩陣加快性能速度 */
@keyframes move {
0%{
transform: matrix(0)
}
25%{
transform: matrix(1, 0, 0, 1, 200, 0)
}
50%{
transform: matrix(1, 0, 0, 1, 200, 200);
}
75%{
transform: matrix(1, 0, 0, 1, 0, 200);
}
}
</style>
</head>
<body>
<div class="container">
<div class="box animation-run"></div>
</div>
</body>
</html>
最終優(yōu)化版本,里面采用了transform做動(dòng)畫阻问,開啟了硬件加速梧税,如果把translate轉(zhuǎn)換成矩陣,進(jìn)行了深度的優(yōu)化
總結(jié)
重排和重繪是DOM編程中耗能的主要原因之一称近,平時(shí)涉及DOM編程時(shí)可以參考以下幾點(diǎn):
- 盡量不要在布局信息改變時(shí)做查詢(會(huì)導(dǎo)致渲染隊(duì)列強(qiáng)制刷新)
- 同一個(gè)DOM的多個(gè)屬性改變可以寫在一起(減少DOM訪問第队,同時(shí)把強(qiáng)制渲染隊(duì)列刷新的風(fēng)險(xiǎn)降為0)
- 如果要批量添加DOM,可以先讓元素脫離文檔流刨秆,操作完后再帶入文檔流凳谦,這樣只會(huì)觸發(fā)一次重排(fragment元素的應(yīng)用)
- 將需要多次重排的元素,position屬性設(shè)為absolute或fixed衡未,這樣此元素就脫離了文檔流尸执,它的變化不會(huì)影響到其他元素家凯。例如有動(dòng)畫效果的元素就最好設(shè)置為絕對(duì)定位。