HTML
呆奕、CSS
养晋、JavaScript
數(shù)據(jù),經(jīng)過瀏覽器中間渲染模塊的處理梁钾,再加上重排
绳泉、重繪
、合成
的一個個環(huán)節(jié)姆泻,才最終輸出為屏幕上的像素
視圖畫面零酪。本文就逐一介紹重排
、重繪
拇勃、合成
的基本概念四苇、觸發(fā)時機、影響范圍以及其優(yōu)化策略方咆。
一瓣赂、瀏覽器渲染原理
一個完整的渲染流程一般都經(jīng)歷如下過程:
- HTML被HTML解析器解析成
DOM Tree
- CSS則被CSS解析器解析成
CSSOM Tree
-
DOM Tree
和CSSOM Tree
解析完成后榆骚,被附加到一起,形成渲染樹(Render Tree
) - 布局煌集,根據(jù)渲染樹計算每個節(jié)點的幾何信息生成
布局樹
(Layout Tree
) - 對布局樹進行分層妓肢,并生成
分層樹
(Layer Tree
) - 為每個圖層生成繪制列表
- 渲染繪制(Paint)。根據(jù)計算好的繪制列表信息繪制整個頁面苫纤,并將其提交到
合成線程
-
合成線程
將圖層分成圖塊碉钠,并在光柵化線程池中將圖塊轉(zhuǎn)換成位圖,發(fā)送繪制圖塊命令DrawQuad
給瀏覽器進程 - 瀏覽器進程根據(jù) DrawQuad 消息生成頁面卷拘,并顯示到顯示器上
二喊废、重排
定義: 當通過JS或css改變了元素的寬度、高度等栗弟,修改了元素的幾何位置屬性操禀,那么瀏覽器會觸發(fā)重新布局,解析之后的一系列子階段横腿,這個過程就叫重排
颓屑。無疑斤寂, 重排需要更新完整的渲染流水線,所以開銷也是最大的揪惦。
觸發(fā)時機和影響范圍: DOM節(jié)點信息更改,觸發(fā)重排時遍搞,這個DOM更改程度會決定周邊DOM更改范圍。
全局范圍: 就是從根節(jié)點html開始對整個渲染樹進行重新布局器腋,例如當我們改變了窗口尺寸或方向或者是修改了根元素的尺寸或者字體大小等溪猿。
局部范圍: 對渲染樹的某部分或某一個渲染對象進行重新布局。
三纫塌、重繪
定義: 如果修改了元素的背景顏色诊县,并沒有引起幾何位置的變換,所以就直接進入了繪制階段措左,然后執(zhí)行之后的一系列子階段依痊,這個過程就叫重繪
。 相較于重排操作怎披,重繪省去了布局和分層階段胸嘁,所以執(zhí)行效率會比重排操作要高一些。
觸發(fā)時機和影響范圍: 每一次的dom更改或者css幾何屬性更改凉逛,都會引起一次瀏覽器的重排/重繪過程性宏,而如果是css的非幾何屬性更改,則只會引起重繪過程状飞。
四毫胜、合成
定義: 合成是一種將頁面的各個部分分離成層(Layer Tree),分別將它們柵格化诬辈,然后在稱為“合成線程”的中組合為頁面的技術(shù)指蚁。
觸發(fā)時機和影響范圍: 在GUI渲染線程后執(zhí)行,將GUI渲染線程生成的繪制列表轉(zhuǎn)換為位圖,然后發(fā)送繪制圖塊命令 DrawQuad 給瀏覽器進程自晰,瀏覽器進程根據(jù) DrawQuad 消息生成頁面,將頁面顯示到顯示器上
優(yōu)點: 我們使用了 CSS 的 transform
來實現(xiàn)動畫效果稍坯,避開了重排
和重繪
階段酬荞,直接在非主線程上執(zhí)行合成動畫操作。這樣的效率是最高的瞧哟,因為是在非主線程上合成混巧,并沒有占用主線程的資源,另外也避開了布局和繪制兩個子階段勤揩,所以相對于重繪和重排咧党,合成能大大提升繪制效率。
五陨亡、常見的觸發(fā)重排傍衡、重繪的屬性和方法
1.引發(fā)重排的操作:
- 頁面首次渲染深员。
- 瀏覽器窗口大小發(fā)生改變——
resize
事件發(fā)生時。 - 元素尺寸或位置發(fā)生改變——定位蛙埂、邊距倦畅、填充、邊框绣的、寬度和高度叠赐。
- 元素內(nèi)容變化(文字數(shù)量或圖片大小等等)。
- 元素字體大小變化屡江。
- 添加或者刪除可見的DOM元素芭概。
- 激活CSS偽類(例如::hover)。
- 設(shè)置style屬性
- 查詢某些屬性或調(diào)用某些方法惩嘉。
2.引起重排屬性和方法:
width罢洲、 display、 clientWidth宏怔、 offsetWidth奏路、 scrollWidth、 scrollIntoView()臊诊、
getBoundingClientRect()鸽粉、 height、 border抓艳、 clientHeight触机、 offsetHeight、
scrollHeight玷或、 scrollTo()儡首、 scrollIntoViewIfNeeded()、 margin偏友、 position蔬胯、
clientTop、 offsetTop位他、 scrollTop氛濒、 getComputedStyle()、 padding鹅髓、 overflow舞竿、
clientLeft、 offsetLeft窿冯、 scrollLeft
3.引起重繪的屬性:
color骗奖、 text-decoration、 outline-color、 outline-width执桌、
border-style鄙皇、 background-image、 outline鼻吮、 box-shadow育苟、
visibility、 background-position椎木、 outline-style违柏、
background-size香椎、 background漱竖、 background-repeat、 border-radius
六畜伐、優(yōu)化策略
1.減少DOM操作
- 最小化DOM訪問次數(shù)馍惹,盡量緩存訪問DOM的樣式信息,避免過度觸發(fā)重排玛界。
- 如果在一個局部方法中需要多次訪問同一個dom万矾,可以在第一次獲取元素時用變量保存下來,減少遍歷時間慎框。
- 用事件委托來減少事件處理器的數(shù)量良狈。
- 用querySelectorAll()替代getElementByXX()。
- querySelectorAll():獲取靜態(tài)集合笨枯,通過函數(shù)獲取元素之后薪丁,元素之后的改變并不會影響之前獲取后存儲到的變量。也就是獲取到元素之后就和html中的這個元素沒有關(guān)系了
- getElementByXX():獲取動態(tài)集合馅精,通過函數(shù)獲取元素之后严嗜,元素之后的改變還是會動態(tài)添加到已經(jīng)獲取的這個元素中。換句話說洲敢,通過這個方法獲取到元素存儲到變量的時候漫玄,以后每一次在Javascript函數(shù)中使用這個變量的時候都會再去訪問一下這個變量對應(yīng)的html元素。
2.減少重排
- 放棄傳統(tǒng)操作DOM的時代压彭,基于vue/react開始數(shù)據(jù)影響視圖模式丽啡。
- 避免設(shè)置大量的style內(nèi)聯(lián)屬性惦费,因為通過設(shè)置style屬性改變結(jié)點樣式的話励稳,每一次設(shè)置都會觸發(fā)一次reflow算凿,所以最好是使用class屬性凳谦。
- 不要使用table布局忆畅,因為table中某個元素一旦觸發(fā)了reflow,那么整個table的元素都會觸發(fā)reflow。那么在不得已使用table的場合家凯,可以設(shè)置table-layout:auto;或者是table-layout:fixed這樣可以讓table一行一行的渲染缓醋,這種做法也是為了限制reflow的影響范圍。
- 盡量少使用display:none可以使用visibility:hidden代替绊诲,display:none會造成重排送粱,visibility:hidden只會造成重繪。
- 使用resize事件時掂之,做防抖和節(jié)流處理抗俄。
- 分離讀寫操作(現(xiàn)代的瀏覽器都有渲染隊列的機制)
- 分離讀寫減少重排的原理
<style>
#box{
width:100px;
height:100px;
border:10px solid #ddd;
}
</style>
<body>
<div id="box"></div>
<script>
// 讀寫分離,一次重排
let box = document.getElementById('box')
box.style.width='200px';//(寫)js改變樣式世舰,加入渲染隊列中动雹,頓一下,查看下一行是否還是修改樣式跟压,如果是則再加入到渲染隊列胰蝠,一直到下一行代碼不是修改樣式為止
box.style.height='200px';//(寫)
box.style.margin='10px';//(寫)
console.log(box.clientWidth);//(讀)
</script>
<script>
// 沒做到讀寫分離,兩次重排
box.style.width='200px';//(寫)js改變樣式震蒋,加入渲染隊列中,頓一下茸塞,下一行不是修改樣式的代碼,瀏覽器就會直接渲染一次(重排)
console.log(box.clientWidth);//(讀)
box.style.height='200px';//(寫)
box.style.margin='10px';//(寫)
</script>
</body>
- 緩存布局信息
<script>
// 兩次重排 ’寫‘操作中包含clientWidth屬性查剖,會刷新渲染隊列
box.style.width = box.clientWidth +10 + 'px'
box.style.height= box.clientHeight +10 + 'px'
</script>
<script>
let a=box.clientWidth //(讀)緩存布局信息
let b=box.clientHeight//(讀)緩存布局信息
// 一次重排
box.style.width = a+10 + 'px' // (寫)
box.style.height= b+10 + 'px' // (寫)
</script>
3.css及優(yōu)化動畫
- 少用css表達式
- 樣式集中改變(批量處理) 減少通過JavaScript代碼修改元素樣式钾虐,盡量使用修改class名方式操作樣式或動畫;
- 可以把動畫效果應(yīng)用到position屬性為absolute或fixed的元素上梗搅,這樣對其他元素影響較小
- 動畫實現(xiàn)的速度的選擇:犧牲平滑度換取速度禾唁。比如實現(xiàn)一個動畫,以1個像素為單位移動這樣最平滑无切,但是reflow就會過于頻繁荡短,大量消耗CPU資源,如果以3個像素為單位移動則會好很多哆键。
- 開啟css3動畫硬件加速(GPU加速)把渲染計算交給GPU掘托。(能用transform做的就不要用其他的,因為transform可以開啟硬件加速籍嘹,而硬件加速可以規(guī)避重排闪盔。直接跳過重排、重繪辱士,走合成進程)
// 向右移動100px泪掀,一次重排
box.style.left='100px'
// 向右移動200px,不會引發(fā)重排
box.style.ctransform='translateX(200)'
七颂碘、總結(jié)
重排
一定會引起重繪
异赫,而重繪
不一定會引起重排
,重繪
的開銷較小,重排
的代價較高塔拳。在日常開發(fā)過程中應(yīng)該盡量減少重排
和重繪
操作鼠证。
歡迎訪問:天問博客