我們從瀏覽器渲染一個(gè)頁(yè)面的行為說(shuō)起:
從服務(wù)器端獲取的HTML文檔中構(gòu)建出DOM樹(shù)(文檔對(duì)象模型);
樣式被加載和分析,構(gòu)建CSSOM(CSS對(duì)象模型);
以DOM和CSSOM為基礎(chǔ),文檔樹(shù)被構(gòu)建,一系列對(duì)象被渲染(Webkit稱每一個(gè)為‘renderer’或’render 對(duì)象(render object)‘,Gecko中稱為‘frame’).渲染樹(shù)反應(yīng)了除不可見(jiàn)元素(
, display:none)之外的DOM結(jié)構(gòu)中一切可見(jiàn)元素,每一段字符串在渲染樹(shù)中都被當(dāng)做獨(dú)立的渲染對(duì)象,每一個(gè)渲染對(duì)象都是其對(duì)應(yīng)的DOM結(jié)構(gòu)和計(jì)算所得樣式的混合體,換句話說(shuō)渲染樹(shù)是DOM樹(shù)的視覺(jué)表現(xiàn);對(duì)于每一個(gè)渲染樹(shù)元素,它的坐標(biāo)都是通過(guò)計(jì)算獲得的,這被稱作排版(布局layout),瀏覽器通過(guò)文檔流的方式(也就是一次就能完成所有元素的布局)(tables需要多于一次的布局);
最終渲染樹(shù)出現(xiàn)在瀏覽器窗口上,這個(gè)過(guò)程稱為繪制(painting)。
當(dāng)用戶與一個(gè)頁(yè)面交互或者腳本修改時(shí),由于文檔結(jié)構(gòu)的變化,以上的一些操作步驟會(huì)重新執(zhí)行。
當(dāng)元素樣式變化并不影響該元素在一個(gè)網(wǎng)頁(yè)上的位置時(shí)( background-color, border-color, visibility),瀏覽器只會(huì)把新樣式應(yīng)用到該元素。
當(dāng)改變影響了 文檔內(nèi)容或結(jié)構(gòu)或者元素的位置時(shí),reflow發(fā)生(重新布局),這一般由以下因素觸發(fā):
DOM操作(添加,刪除,更改,或者變更元素順序);
內(nèi)容改變(包括表格區(qū)域內(nèi)文本的變化)(所占位置大小變了);
計(jì)算或者改變CSS屬性(位置改變);
添加或者刪除樣式表;
改變類屬性(class)(可能會(huì)改變位置);
瀏覽器窗口操作(改變大小,滾動(dòng));
偽類激活(可能改變位置)
瀏覽器會(huì)盡可能的限制被改變?cè)厮趨^(qū)域的重排重繪,比如說(shuō) display:fixed/absolute元素改變時(shí)只會(huì)影響它本身和它的子元素,而 display:static元素改變時(shí)會(huì)使其隨后的元素都被重繪;(影響盡量少的元素)
另一個(gè)最大化性能的機(jī)制在于,當(dāng)運(yùn)行一系列JavaScript片段時(shí),瀏覽器會(huì)緩存它們,然后一次運(yùn)行。看下面的例子可以很好的理解:
var $body = $('body');$body.css('padding', '1px'); // reflow, repaint$body.css('color', 'red'); // repaint$body.css('margin', '2px'); // reflow, repaint// only 1 reflow and repaint will actually happen(由于緩存,只會(huì)重繪一次)
然而,就像上面已經(jīng)提到的,調(diào)用一個(gè)元素的屬性會(huì)觸發(fā)強(qiáng)制性的reflow,當(dāng)我們加上一行讀元素屬性的代碼時(shí)就會(huì)發(fā)生;
var $body = $('body');$body.css('padding', '1px');$body.css('padding'); // reading a property, a forced reflow(強(qiáng)制發(fā)生)$body.css('color', 'red');$body.css('margin', '2px');//另外一次reflow
因此,會(huì)有兩次reflow,因此應(yīng)該組合來(lái)讀元素屬性已最大化性能 一個(gè)詳細(xì)的例子;
$(function() { var $body = $('body'); $body .on('click', '.block-1', function(e) { // 1 reflow $body.css('padding', '1px'); $body.css('color', 'red'); $body.css('margin', '2px'); }) .on('click', '.block-2', function(e) { // 2 reflows $body.css('padding', '1px'); $body.css('padding'); $body.css('color', 'red'); $body.css('margin', '2px'); }) .on('click', '.block-3', function(e) { // 3 repaints $body.css('color', 'red'); $body.css('color'); $body.css('color', 'yellow'); $body.css('background'); $body.css('color', 'blue'); $body.css('outline'); }) .on('click', '.block-4', function(e) { // 1 repaint $body.css('color', 'red'); $body.css('color', 'yellow'); $body.css('color', 'blue'); $body.css('color'); $body.css('background'); $body.css('outline'); }) .on('click', '.block-5', function(e) { // 3 reflows $body.css('padding', '1px'); $body[0].offsetHeight; $body.css('padding', '2px'); $body[0].offsetTop; $body.css('padding', '3px'); $body[0].offsetWidth; }) .on('click', '.block-6', function(e) { // 1 reflow $body.css('padding', '1px'); $body.css('padding', '2px'); $body.css('padding', '3px'); $body[0].offsetHeight; $body[0].offsetTop; $body[0].offsetWidth; });});
有些時(shí)候,可能你會(huì)需要一次強(qiáng)制性的reflow,例如:我們需要運(yùn)用兩次 margin-left到同一個(gè)對(duì)象,第一次無(wú)動(dòng)畫的設(shè)置到100px,然后通過(guò)動(dòng)畫過(guò)渡到50px, 實(shí)例;
過(guò)渡動(dòng)畫:
.has-transition { -webkit-transition: margin-left 1s ease-out; -moz-transition: margin-left 1s ease-out; -o-transition: margin-left 1s ease-out; transition: margin-left 1s ease-out;}
// our element that has a "has-transition" class by defaultvar $targetElem = $('#targetElemId');// remove the transition class$targetElem.removeClass('has-transition');// change the property expecting the transition to be off, as the class is not there// anymore$targetElem.css('margin-left', 100);// put the transition class back$targetElem.addClass('has-transition');// change the property$targetElem.css('margin-left', 50);
上述代碼并不按預(yù)期工作,因?yàn)楦淖儽痪彺娌⒃谧詈髨?zhí)行了一次,這時(shí)候我們就需要一次強(qiáng)制性的執(zhí)行了:
// remove the transition class$(this).removeClass('has-transition');// change the property$(this).css('margin-left', 100);// trigger a forced reflow, so that changes in a class/property get applied immediately$(this)[0].offsetHeight; // an example, other properties would work, too// put the transition class back$(this).addClass('has-transition');// change the property$(this).css('margin-left', 50);
現(xiàn)在達(dá)到預(yù)期效果了!
總結(jié)了一些有用的信息,本文有以下建議
構(gòu)建有效的HTML和CSS,不要忘記聲明文檔編碼方式,樣式表應(yīng)該包含在
標(biāo)簽內(nèi),腳本文件應(yīng)該放在 標(biāo)簽的底部;簡(jiǎn)化并且充分利用CSS選擇器(這一條被大多數(shù)使用CSS預(yù)處理器的開(kāi)發(fā)者忽略),維護(hù)最少的層狀結(jié)構(gòu),以下是各選擇器的效率排行
#id.classdiva+iul>li*input[type='text']a:hover
應(yīng)該引起注意的是,瀏覽器是從右向左讀取選擇器的,所以最右邊的應(yīng)該選擇效率比較高的選擇器 #id,.class;
div * {...} // bad.list li {...} // bad.list-item {...} // good#list .list-item {...} // good
在腳本中,應(yīng)該盡可能的減少DOM操作,如果對(duì)象和屬性會(huì)被重用,就緩存它們。在最好離線元素(未被插入文檔樹(shù))(offline)上進(jìn)行操作,然后把它插入DOM結(jié)構(gòu)中;
如果使用jQuery,遵循[jQuery選擇器基本原則( http://learn.jquery.com/performance/optimize-selectors/);
修改元素的樣式時(shí),修改class屬性是做好的方法,其位置越深,越好(also because this helps decouple logic from presentation);
只使 display:fixed/absolute的元素具有動(dòng)畫;
不適用復(fù)雜的 :hover動(dòng)畫也是一個(gè)好的實(shí)踐(給
添加 no-hover屬性), 延展閱讀;延展閱讀已獲得更多信息:
How browsers works;
Rendering: repaint, reflow/relayout, restyle;
原文鏈接
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com