站前端的用戶體驗(yàn),決定了用戶是否想要繼續(xù)使用網(wǎng)站以及網(wǎng)站的其他功能,網(wǎng)站的用戶體驗(yàn)佳,可留住更多的用戶。除此之外,前端優(yōu)化得好,還可以為企業(yè)節(jié)約成本。那么我們應(yīng)該如何對我們前端的頁面進(jìn)行性能優(yōu)化呢?
前端性能優(yōu)化可以分為三個方面:接口訪問優(yōu)化、靜態(tài)資源優(yōu)化和頁面渲染速度優(yōu)化。
1.1、減少http請求,合理設(shè)置 HTTP緩存
http協(xié)議是無狀態(tài)的應(yīng)用層協(xié)議,每次發(fā)送http請求時,都需要建立連接、通信、斷開連接,在服務(wù)器端每個http都需要開啟獨(dú)立的線程去處理。所以盡量減少http請求,盡可能地提高訪問性能。
減少http請求的方法:
1.2、減少cookie傳輸
cookie 存在于 http 頭,在客戶端與服務(wù)器之間交換,盡可能地控制 cookie 的大小,cookie越小,響應(yīng)速度越快,減少 cookie 傳輸,響應(yīng)速度更快。
1.3、使用CDN提供靜態(tài)文件
使用 CDN 可以更快地在全球范圍內(nèi)獲取到你的靜態(tài)文件,加快網(wǎng)頁加載。
1.4、啟用 GZIP 壓縮
http 協(xié)議上 GZIP 編碼,是一種用來改進(jìn) web 應(yīng)用程序的。開啟 GZIP 后,服務(wù)器會把網(wǎng)頁內(nèi)容壓縮后傳輸,一般能壓縮到原大小40%,這樣網(wǎng)頁傳輸速度就更快了。GZIP 有兩大好處:一是減少存儲空間,二是通過網(wǎng)絡(luò)傳輸文件時,可以減少傳輸時間。
1.5、分域存放資源
HTTP 客戶端一般對同一個服務(wù)器的并發(fā)連接個數(shù)都是有限制的,通常最大并行連接為四了,剩下的會進(jìn)入等待隊(duì)列,等前邊的執(zhí)行完畢,等待的才會執(zhí)行。所以利用多域名主機(jī)存放資源,增加并行連接量,縮短資源加載時間。
1.6、減少頁面重定向
開啟 https 可以有效防范攻擊,保證用戶始終訪問到網(wǎng)站的加密連接,保護(hù)數(shù)據(jù)安全,同時省去 301/302 跳轉(zhuǎn)的時間,大大提升網(wǎng)站的安全系數(shù)和用戶體驗(yàn)。
如果在網(wǎng)站設(shè)置當(dāng)用戶訪問域名的時候強(qiáng)制 https 進(jìn)行 301 或者 302 跳轉(zhuǎn),但是這個過程中,用到 HTTP 因此容易發(fā)生劫持,受到第三方的攻擊。所以盡可能使用https安全。
1.7、避免使用iframe
iframe 相當(dāng)于本頁面又嵌套了一個頁面,消耗性能,還要加載嵌套頁面的資源,所以更消耗時間。
1.8、借用瀏覽器緩存
ajax 請求到的數(shù)據(jù),可以緩存到瀏覽器,下次使用的時候無需再次獲取,直接取緩存數(shù)據(jù)就可以。這個會根據(jù)具體的項(xiàng)目來做,比如常用的角色類型就會緩存,獲取到的普通數(shù)據(jù)為了保證實(shí)時性,不能使用緩存。
2.1、壓縮 html、css、js 等文件
刪除不必要的空格、注釋和中行,減少文件大小,顯著減少用戶下載時間,加快網(wǎng)頁加載速度。可以直接使用壓縮工具,可以自動刪除所有不必要內(nèi)容。
2.2、在 js 之前引用 css
這是一個小細(xì)節(jié),js 執(zhí)行的時候會進(jìn)入阻塞,如果放入 js 之后加載,會等待 js 執(zhí)行完成之后才能加載 css,渲染頁面,此時就會出現(xiàn)布局錯亂。所以 css 文件需要非阻塞引入,以防DOM 花費(fèi)更多時間才能渲染。
2.3、非阻塞 js
js 會阻止 html 文檔的正常解析,當(dāng)解析器到達(dá) script 標(biāo)記時,它會停止解析并執(zhí)行腳本。所以我們經(jīng)常把 script 引入的 js,放到 html 中最底下。如果需要讓腳本位于頁面頂部,建議添加非阻塞屬性。經(jīng)常使用 defer 和 async 來異步加載js文件。
<!-- 使用defer -->
<script defer src="foo.js" ></script>
<!-- 使用async -->
<script async src="foo.js"></script>
2.4、圖片壓縮
最常見的就是 css 雪碧,就是將很多很多的小圖標(biāo)放在一張圖片上,就稱為雪碧圖。雪碧圖最大優(yōu)點(diǎn)就是可以減少http請求,除此也能壓縮圖片文件大小。使用的時候,通過設(shè)置 background-position ,移動圖片的位置。除此之外,網(wǎng)站用到的大圖,也需要在保證圖片質(zhì)量前提下優(yōu)化到最小。
2.5、矢量圖替代位圖
矢量圖(SVG)往往比圖像小很多,縮放的時候不失真,這些圖像還可以通過 css 進(jìn)行動畫和修改,比位圖方便控制。可以的話,盡量用矢量圖多點(diǎn)。
2.6、js代碼相關(guān)優(yōu)化
3.1、懶加載
素材類的網(wǎng)站,頁面一屏展示很多圖片,而且圖片還不能失真,圖片加載太多,網(wǎng)頁加載慢得很,所以就引用懶加載,只加載可視區(qū)的圖片,避免加載可以能不需要或不必要的圖像。改善頁面的響應(yīng)時間。
3.2、避免響應(yīng)式布局
響應(yīng)式網(wǎng)站雖然能夠兼容所有終端設(shè)備,但是會出現(xiàn)隱藏部分無用內(nèi)容,浪費(fèi)帶寬,加載時間還長,頁面的渲染時間也長。想更多了解響應(yīng)式布局,請點(diǎn)擊《前端響應(yīng)式布局為什么是個坑?》。
3.3、設(shè)置大小,避免重繪
遇到 img 標(biāo)簽,會立馬發(fā)送一個 http 請求,下載圖片,頁面繼續(xù)向下渲染,等圖片加載成功了,發(fā)現(xiàn)圖片的寬高大小發(fā)生變化,影響后邊排版,所以頁面會重新再繪制一次這部分。所以盡可能設(shè)置圖片的大小。
3.4、減少DOM元素
解析 html 內(nèi)容,將標(biāo)簽轉(zhuǎn)化為DOM節(jié)點(diǎn),之后再解析其他文件,DOM元素越少,也就是標(biāo)簽越少,文件轉(zhuǎn)化得越快,加載速度也就快了。
3.5、減少 Flash 的使用
flash 文件比較大,加載起來耗時。除此,flash 插件還需要運(yùn)行才能運(yùn)行,最主要有些瀏覽器flash插件馬上要下線了,建議盡量不用 flash。
3.6、文件順序
css文件放在最頂部,優(yōu)先渲染。js放在最底部,避免阻塞。
讓網(wǎng)頁如何加載更快,有好多的細(xì)節(jié),還是要好好提升自己的技能~~~~~~~~~
點(diǎn)個關(guān)注!更多分享內(nèi)容。
CSS in JS是一種解決css問題想法的集合,而不是一個指定的庫。從CSS in JS的字面意思可以看出,它是將css樣式寫在JavaScript文件中,而不需要獨(dú)立出.css、.less之類的文件。將css放在js中使我們更方便的使用js的變量、模塊化、tree-shaking。還解決了css中的一些問題,譬如:更方便解決基于狀態(tài)的樣式,更容易追溯依賴關(guān)系,生成唯一的選擇器來鎖定作用域。盡管CSS in JS不是一個很新的技術(shù),但國內(nèi)的普及程度并不高。由于Vue和Angular都有屬于他們自己的一套定義樣式的方案,React本身也沒有管用戶怎樣定義組件的樣式[1],所以CSS in JS在React社區(qū)的熱度比較高。
目前為止實(shí)現(xiàn)CSS in JS的第三方庫有很多:(http://michelebertoli.github.io/css-in-js/)。像JSS[2]、styled-components[3]等。在這里我們就不展開贅述了(相關(guān)鏈接已放在下方),這篇文章的重點(diǎn)是JS in CSS。
在上面我們提到CSS in JS就是把CSS寫在JavaScript中,那么JS in CSS我們可以推斷出就是可以在CSS中使用JavaScript腳本,如下所示。可以在CSS中編寫Paint API的功能。還可以訪問:ctx,geom。甚至我們還可以編寫自己的css自定義屬性等。這些功能的實(shí)現(xiàn)都基于CSS Houdini[4]。
.el {
--color: cyan;
--multiplier: 0.24;
--pad: 30;
--slant: 20;
--background-canvas: (ctx, geom) => {
let multiplier = var(--multiplier);
let c = `var(--color)`;
let pad = var(--pad);
let slant = var(--slant);
ctx.moveTo(0, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
ctx.lineTo(0, geom.height);
ctx.fillStyle = c;
ctx.fill();
};
background: paint(background-canvas);
transition: --multiplier .4s;
}
.el:hover {
--multiplier: 1;
}
在如今的Web開發(fā)中,JavaScript幾乎占據(jù)了項(xiàng)目代碼的大部分。我們可以在項(xiàng)目開發(fā)中使用ES 2020、ES2021、甚至提案中的新特性(如:Decorator[5]),即使瀏覽器尚未支持,也可以編寫Polyfill或使用Babel之類的工具進(jìn)行轉(zhuǎn)譯,讓我們可以將最新的特性應(yīng)用到生產(chǎn)環(huán)境中(如下圖所示)。
JavaScript標(biāo)準(zhǔn)制定流程.png
而CSS就不同了,除了制定CSS標(biāo)準(zhǔn)規(guī)范所需的時間外,各家瀏覽器的版本、實(shí)戰(zhàn)進(jìn)度差異更是曠日持久(如下圖所示),最多利用PostCSS、Sass等工具來幫我們轉(zhuǎn)譯出瀏覽器能接受的CSS。開發(fā)者們能操作的就是通過JS去控制DOM與CSSOM來影響頁面的變化,但是對于接下來的Layout、Paint與Composite就幾乎沒有控制權(quán)了。為了解決上述問題,為了讓CSS的魔力不在受到瀏覽器的限制,Houdini就此誕生。
CSS 標(biāo)準(zhǔn)制定流程.png
我們上文中提到JavaScript中進(jìn)入提案中的特性我們可以編寫Polyfill,只需要很短的時間就可以講新特性投入到生產(chǎn)環(huán)境中。這時,腦海中閃現(xiàn)出的第一個想法就是CSS Polyfill,只要CSS的Polyfill 足夠強(qiáng)大,CSS或許也能有JavaScript一樣的發(fā)展速度,令人可悲的是編寫CSS Polyfill異常的困難,并且大多數(shù)情況下無法在不破壞性能的情況下進(jìn)行。這是因?yàn)镴avaScript是一門動態(tài)腳本語言[6]。它帶來了極強(qiáng)的擴(kuò)展性,正是因?yàn)檫@樣,我們可以很輕松使用JavaScript做出JavaScript的Polyfill。但是CSS不是動態(tài)的,在某些場景下,我們可以在編譯時將一種形式的CSS的轉(zhuǎn)換成另一種(如PostCSS[7])。如果你的Polyfill依賴于DOM結(jié)構(gòu)或者某一個元素的布局、定位等,那么我們的Polyfill就無法編譯時執(zhí)行,而需要在瀏覽器中運(yùn)行了。不幸的是,在瀏覽器中實(shí)現(xiàn)這種方案非常不容易。
頁面渲染流程.png
如上圖所示,是從瀏覽器獲取到HTML到渲染在屏幕上的全過程,我們可以看到只有帶顏色(粉色、藍(lán)色)的部分是JavaScript可以控制的環(huán)節(jié)。首先我們根本無法控制瀏覽器解析HTML與CSS并將其轉(zhuǎn)化為DOM與CSSOM的過程,以及Cascade,Layout,Paint,Composite我們也無能為力。整個過程中我們唯一完全可控制的就是DOM,另外CSSOM部分可控。
CSS Houdini草案中提到,這種程度的暴露是不確定的、兼容性不穩(wěn)定的以及缺乏對關(guān)鍵特性的支持的。比如,在瀏覽器中的 CSSOM 是不會告訴我們它是如何處理跨域的樣式表,而且對于瀏覽器無法解析的 CSS 語句它的處理方式就是不解析了,也就是說——如果我們要用 CSS polyfill讓瀏覽器去支持它尚且不支持的屬性,那就不能在 CSSOM 這個環(huán)節(jié)做,我們只能遍歷一遍DOM,找到 <style> 或 <link rel="stylesheet"> 標(biāo)簽,獲取其中的 CSS 樣式、解析、重寫,最后再加回 DOM 樹中。令人尷尬的是,這樣DOM樹全部刷新了,會導(dǎo)致頁面的重新渲染(如下如所示)。
即便如此,有的人可能會說:“除了這種方法,我們也別無選擇,更何況對網(wǎng)站的性能也不會造成很大的影響”。那么對于部分網(wǎng)站是這樣的。但如果我們的Polyfill是需要對可交互的頁面呢?例如scroll,resize,mousemove,keyup等等,這些事件隨時會被觸發(fā),那么意味著隨時都會導(dǎo)致頁面的重新渲染,交互不會像原本那樣絲滑,甚至導(dǎo)致頁面崩潰,對用戶的體驗(yàn)也極其不好。
綜上所述,如果我們想讓瀏覽器解析它不認(rèn)識的樣式(低版本瀏覽器使用grid布局),然而渲染流程我們無法介入,我們也只能通過手動更新DOM的方式,這樣會帶來很多問題,Houdini的出現(xiàn)正是致力于解決他們。
Houdini是一組底層API,它公開了CSS引擎的各個部分,如下圖所示展示了每個環(huán)節(jié)對應(yīng)的新API(灰色部分各大瀏覽器還未實(shí)現(xiàn)),從而使開發(fā)人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴(kuò)展CSS。Houdini是一群來自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程師組成的工作小組設(shè)計(jì)而成的。它們使開發(fā)者可以直接訪問CSS對象模型(CSSOM),使開發(fā)人員可以編寫瀏覽器可以解析為CSS的代碼,從而創(chuàng)建新的CSS功能,而無需等待它們在瀏覽器中本地實(shí)現(xiàn)。
CSS Houdini-API
盡管當(dāng)前已經(jīng)有了CSS變量,可以讓開發(fā)者控制屬性值,但是無法約束類型或者更嚴(yán)格的定義,CSS Houdini新的API,我們可以擴(kuò)展css的變量,我們可以定義CSS變量的類型,初始值,繼承。它是css變量更強(qiáng)大靈活。
CSS變量現(xiàn)狀:
.dom {
--my-color: green;
--my-color: url('not-a-color'); // 它并不知道當(dāng)前的變量類型
color: var(--my-color);
}
Houdini提供了兩種自定義屬性的注冊方式,分別是在js和css中。
CSS.registerProperty({
name: '--my-prop', // String 自定義屬性名
syntax: '<color>', // String 如何去解析當(dāng)前的屬性,即屬性類型,默認(rèn) *
inherits: false, // Boolean 如果是true,子節(jié)點(diǎn)將會繼承
initialValue: '#c0ffee', // String 屬性點(diǎn)初始值
});
我們還可以在css中注冊,也可以達(dá)到上面的效果
@property --my-prop {
syntax: '<color>';
inherits: false;
initial-value: #c0ffee;
}
這個API中最令人振奮人心的功能是自定義屬性上添加動畫,像這樣:transition: --multiplier 0.4s;,這個功能我們在前面介紹什么是js in css那個demo[8]用使用過。我們還可以使用+使syntax屬性支持一個或多個類型,也可以使用|來分割。更多syntax屬性值:
屬性值描述<length>長度值<number>數(shù)字<percentage>百分比<length-percentage>長度或百分比,calc將長度和百分比組成的表達(dá)式<color>顏色<image>圖像<url>網(wǎng)址<integer>整數(shù)<angle>角度<time>時間<resolution>分辨率<transform-list>轉(zhuǎn)換函數(shù)<custom-ident>ident
Worklets是渲染引擎的擴(kuò)展,從概念上來講它類似于Web Workers[9],但有幾個重要的區(qū)別:
Worklet是一個JavaScript模塊,通過調(diào)用worklet的addModule方法(它是個Promise)來添加。比如registerLayout,registerPaint, registerAnimator 我們都需要放在Worklet中
//加載單個
await demoWorklet.addModule('path/to/script.js');
// 一次性加載多個worklet
Promise.all([
demoWorklet1.addModule('script1.js'),
demoWorklet2.addModule('script2.js'),
]).then(results => {});
registerDemoWorklet('name', class {
// 每個Worklet可以定義要使用的不同函數(shù)
// 他們將由渲染引擎在需要時調(diào)用
process(arg) {
return !arg;
}
});
Worklets的生命周期
Worklets lifecycle
Typed OM是對現(xiàn)有的CSSOM的擴(kuò)展,并實(shí)現(xiàn) Parsing API 和 Properties & Values API相關(guān)的特性。它將css值轉(zhuǎn)化為有意義類型的JavaScript的對象,而不是像現(xiàn)在的字符串。如果我們嘗試將字符串類型的值轉(zhuǎn)化為有意義的類型并返回可能會有很大的性能開銷,因此這個API可以讓我們更高效的使用CSS的值。
現(xiàn)在讀取CSS值增加了新的基類CSSStyleValue,他有許多的子類可以更加精準(zhǔn)的描述css值的類型:
子類描述CSSKeywordValueCSS關(guān)鍵字和其他標(biāo)識符(如inherit或grid)CSSPositionValue位置信息 (x,y)CSSImageValue表示圖像的值屬性的對象CSSUnitValue表示為具有單個單位的單個值(例如50px),也可以表示為沒有單位的單個值或百分比CSSMathValue比較復(fù)雜的數(shù)值,比如有calc,min和max。這包括子類 CSSMathSum, CSSMathProduct, CSSMathMin,CSSMathMax, CSSMathNegate 和 CSSMathInvertCSSTransformValue由CSS transforms組成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspective 和 CSSMatrixComponent
使用Typed OM主要有兩種方法:
使用attributeStyleMap設(shè)置并獲取
myElement.attributeStyleMap.set('font-size', CSS.em(2));
myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }
myElement.attributeStyleMap.set('opacity', CSS.number(.5));
myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };
在線demo[10]
使用computedStyleMap
.foo {
transform: translateX(1em) rotate(50deg) skewX(10deg);
vertical-align: baseline;
width: calc(100% - 3em);
}
const cs = document.querySelector('.foo').computedStyleMap();
cs.get('vertical-align');
// CSSKeywordValue {
// value: 'baseline',
// }
cs.get('width');
// CSSMathSum {
// operator: 'sum',
// length: 2,
// values: CSSNumericArray {
// 0: CSSUnitValue { value: -90, unit: 'px' },
// 1: CSSUnitValue { value: 100, unit: 'percent' },
// },
// }
cs.get('transform');
// CSSTransformValue {
// is2d: true,
// length: 3,
// 0: CSSTranslate {
// is2d: true,
// x: CSSUnitValue { value: 20, unit: 'px' },
// y: CSSUnitValue { value: 0, unit: 'px' },
// z: CSSUnitValue { value: 0, unit: 'px' },
// },
// 1: CSSRotate {...},
// 2: CSSSkewX {...},
// }
開發(fā)者可以通過這個API實(shí)現(xiàn)自己的布局算法,我們可以像原生css一樣使用我們自定義的布局(像display:flex, display:table)。在Masonry layout library[11] 上我們可以看到開發(fā)者們是有多想實(shí)現(xiàn)各種各樣的復(fù)雜布局,其中一些布局光靠 CSS 是不行的。雖然這些布局會讓人耳目一新印象深刻,但是它們的頁面性能往往都很差,在一些低端設(shè)備上性能問題猶為明顯。
CSS Layout API 暴露了一個registerLayout方法給開發(fā)者,接收一個布局名(layout name)作為后面在 CSS中使用的屬性值,還有一個包含有這個布局邏輯的JavaScript類。
my-div {
display: layout(my-layout);
}
// layout-worklet.js
registerLayout('my-layout', class {
static get inputProperties() { return ['--foo']; }
static get childrenInputProperties() { return ['--bar']; }
async intrinsicSizes(children, edges, styleMap) {}
async layout(children, edges, constraints, styleMap) {}
});
await CSS.layoutWorklet.addModule('layout-worklet.js');
目前瀏覽器大部分還不支持
我們可以在CSS background-image中使用它,我們可以使用Canvas 2d上下文,根據(jù)元素的大小控制圖像,還可以使用自定義屬性。
await CSS.paintWorklet.addModule('paint-worklet.js');
registerPaint('sample-paint', class {
static get inputProperties() { return ['--foo']; }
static get inputArguments() { return ['<color>']; }
static get contextOptions() { return {alpha: true}; }
paint(ctx, size, props, args) { }
});
這個API讓我們可以控制基于用戶輸入的關(guān)鍵幀動畫,并且以非阻塞的方式。還能更改一個 DOM 元素的屬性,不過是不會引起渲染引擎重新計(jì)算布局或者樣式的屬性,比如 transform、opacity 或者滾動條位置(scroll offset)。Animation API的使用方式與 Paint API 和Layout API略有不同我們還需要通過new一個WorkletAnimation來注冊worklet。
// animation-worklet.js
registerAnimator('sample-animator', class {
constructor(options) {
}
animate(currentTime, effect) {
effect.localTime = currentTime;
}
});
await CSS.animationWorklet.addModule('animation-worklet.js');
// 需要添加動畫的元素
const elem = document.querySelector('#my-elem');
const scrollSource = document.scrollingElement;
const timeRange = 1000;
const scrollTimeline = new ScrollTimeline({
scrollSource,
timeRange,
});
const effectKeyframes = new KeyframeEffect(
elem,
// 動畫需要綁定的關(guān)鍵幀
[
{transform: 'scale(1)'},
{transform: 'scale(.25)'},
{transform: 'scale(1)'}
],
{
duration: timeRange,
},
);
new WorkletAnimation(
'sample-animator',
effectKeyframes,
scrollTimeline,
{},
).play();
關(guān)于此API的更多內(nèi)容:(https://github.com/w3c/css-houdini-drafts/tree/main/css-animation-worklet-1)
允許開發(fā)者自由擴(kuò)展 CSS 詞法分析器。
解析規(guī)則:
const background = window.cssParse.rule("background: green");
console.log(background.styleMap.get("background").value) // "green"
const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
console.log(styles.length) // 5
console.log(styles[0].styleMap.get("margin-top").value) // 5
console.log(styles[0].styleMap.get("margin-top").type) // "px"
解析CSS:
const style = fetch("style.css")
.then(response => CSS.parseStylesheet(response.body));
style.then(console.log);
它將提供一些方法來測量在屏幕上呈現(xiàn)的文本元素的尺寸,將允許開發(fā)者控制文本元素在屏幕上呈現(xiàn)的方式。使用當(dāng)前功能很難或無法測量這些值,因此該API將使開發(fā)者可以更輕松地創(chuàng)建與文本和字體相關(guān)的CSS特性。例如:
Is Houdini ready yet
(https://ishoudinireadyyet.com/)
了解到這里,部分開發(fā)者可能會說:“我不需要這些花里胡哨的技術(shù),并不能帶收益。我只想簡簡單單的寫幾個頁面,做做普通的Web App,并不想試圖干預(yù)瀏覽器的渲染過程從而實(shí)現(xiàn)一些實(shí)驗(yàn)性或炫酷的功能。”如果這樣想的話,我們不妨退一步再去思考。回憶下最近做過的項(xiàng)目,用于實(shí)現(xiàn)頁面效果所使用到的技術(shù),grid布局方式在考慮兼容老版本瀏覽器時也不得不放棄。我們想控制瀏覽器渲染頁面的過程并不是僅僅為了炫技,更多的是為了幫助開發(fā)者們解決以下兩個問題:
幾年過后再回眸,當(dāng)主流瀏覽器完全支持Houdini的時候。我們可以在瀏覽器上隨心所欲的使用任何CSS屬性,并且他們都能完美支持。像今天的grid布局在舊版本瀏覽器支持的并不友好的這類問題,那時我們只需要安裝對應(yīng)的Polyfill就能解決類似的問題。
覽器的工作機(jī)制,一句話概括起來就是:web瀏覽器與web服務(wù)器之間通過HTTP協(xié)議進(jìn)行通信的過程。所以,C/S之間握手的協(xié)議就是HTTP協(xié)議。瀏覽器接收完畢開始渲染之前大致過程如下:
從瀏覽器地址欄的請求鏈接開始,瀏覽器通過DNS解析查到域名映射的IP地址,成功之后瀏覽器端向此IP地址取得連接,成功連接之后,瀏覽器端將請 求頭信息 通過HTTP協(xié)議向此IP地址所在服務(wù)器發(fā)起請求,服務(wù)器接受到請求之后等待處理,最后向?yàn)g覽器端發(fā)回響應(yīng),此時在HTTP協(xié)議下,瀏覽器從服務(wù)器接收到 text/html類型的代碼,瀏覽器開始顯示此html,并獲取其中內(nèi)嵌資源地址,然后瀏覽器再發(fā)起請求來獲取這些資源,并在瀏覽器的html中顯示。
離我們最近并能直接顯示一個完整通信過程的工具就是Firebug了,看下圖:
其中黃色的tips浮層告訴了我們”colorBox.html”從發(fā)起請求到關(guān)閉連接整個過程中每個環(huán)節(jié)的時長(域名解析 -> 建立連接 -> 發(fā)起請求 -> 等待響應(yīng) -> 接收數(shù)據(jù)),點(diǎn)擊該請求,可以獲得HTTP的headers信息,包含響應(yīng)頭信息與請求頭信息,如:
//響應(yīng)頭信息 HTTP/1.1 304
Server: Apache/2.2.4 (Win32) PHP/5.2.1 Connection: Keep-Alive Keep-Alive: timeout=5, max=100 Etag: "1e483-1324-a86f5621"
//請求頭信息 GET /Docs/eva/api/colorBox.html HTTP/1.1 Host: ued.com User-Agent: Mozilla/5.0
Firefox/3.6.13 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://ued.com/Docs/ If-Modified-Since: Thu, 17 Feb 2011 10:14:07 GMT If-None-Match: "1e483-1324-a86f5621" Cache-Control: max-age=0
另外,ajax異步請求同樣遵循HTTP協(xié)議,原理大同小異。
瀏覽器加載顯示html頁面內(nèi)容的順序
我們經(jīng)常看到瀏覽器在加載某個頁面時,部分內(nèi)容先顯示出來,又有些內(nèi)容后顯示。那么瀏覽器加載顯示html究竟是按什么順序進(jìn)行的呢?
其實(shí)瀏覽器加載顯示html的順序是按下面的順序進(jìn)行的:
1、IE下載的順序是從上到下,渲染的順序也是從上到下,下載和渲染是同時進(jìn)行的。
2、在渲染到頁面的某一部分時,其上面的所有部分都已經(jīng)下載完成(并不是說所有相關(guān)聯(lián)的元素都已經(jīng)下載完)。
3、如果遇到語義解釋性的標(biāo)簽嵌入文件(JS腳本,CSS 劍 敲創(chuàng)聳盜E的下載過程會啟用單獨(dú)連接進(jìn)行下載。
4、并且在下載后進(jìn)行解析,解析過程中,停止頁面所有往下元素的下載。
5、樣式表在下載完成后,將和以前下載的所有樣式表一起進(jìn)行解析,解析完成后,將對此前所有元素(含以前已經(jīng)渲染的)重新進(jìn)行渲染。
6、JS、CSS中如有重定義,后定義函數(shù)將覆蓋前定義函數(shù)。
Firefox處理下載和渲染順序大體相同,只是在細(xì)微之處有些差別,例如:iframe的渲染
如果你的網(wǎng)頁比較大,希望部分內(nèi)容先顯示出來,粘住瀏覽者,那么你可以按照上面的規(guī)則合理的布局你的網(wǎng)頁,達(dá)到預(yù)期的目的。
JS的加載
不能并行下載和解析(阻塞下載)
當(dāng) 引用了JS的時候,瀏覽器發(fā)送1個jsrequest就會一直等待該request的返回。因?yàn)闉g覽器需要1個穩(wěn)定的DOM樹結(jié)構(gòu),而JS中很有可能有代 碼直接改變了DOM樹結(jié)構(gòu),比如使用 document.write 或 appendChild,甚至是直接使用的location.href進(jìn)行跳轉(zhuǎn),瀏覽器為了防止出現(xiàn)JS修改DOM樹,需要重新構(gòu)建DOM樹的情況,所以 就會阻塞其他的下載和呈現(xiàn).
為了更清楚的顯示頁面元素的加載順序,動手寫了一個程序,程序?qū)撁嬷械拿總€元素都延遲10秒。
程序的位置在見附件。
首先查看TestHtmlOrder.aspx這個頁面,使用HttpWatcher來檢測頁面元素的加載。
從下面的圖中可以看到加載順序。
IE首先加載了主頁面TestHtmlOrder.aspx,
下載了主頁面后,頁面首先顯示的是“紅色劍靈”、“藍(lán)色劍靈”幾個字,但此時顯示的是只是黑色字體,沒有樣式,因?yàn)闃邮竭€沒有下載下來。
接下來頁面中的標(biāo)簽是JS標(biāo)簽,屬于嵌入文件,因此IE需要將其下載下來。這有兩個文件,雖然IE同時能夠和WebServer建立兩個鏈接,但是此時并沒有使用兩個連接,而是使用一個連接,在下載完成后,接下來才下載另外一個文件。
究其原因,是因?yàn)镴S包含了語法定義,在第二個文件里面的函數(shù)可能用到了第一個文件里面的變量和函數(shù),IE沒有辦法判斷,或者需要很耗時的判斷,才 能判斷文件下載的先后順序。而在解釋方面,IE對JS文件是下載一個,解釋一個(可以執(zhí)行文件TestJsOrder2.aspx)。如果先下載的是第二 個文件,此時就會發(fā)生解釋錯誤。因此需要開發(fā)者自己在放置JS文件位置時,按先后順序放好,IE依次下載進(jìn)行解釋。后面的函數(shù)覆蓋前面的函數(shù)定義
在下載完成后,我們看到helloWorld,helloworld2,開始順序執(zhí)行。而此時字體的樣式表和圖片仍然沒有下載下來。
在helloWorld,helloWorld2執(zhí)行過程時,此時頁面停留在函數(shù)執(zhí)行的中斷點(diǎn)(alert部分)。此時IE并沒有去下載CSS的文件。由此說明JS函數(shù)的執(zhí)行會阻塞IE的下載。
接下來我們看到CSS文件的下載也是使用了一個連接,也是串行下載。其串行下載的原因和JS串行下載原因是一樣的。
在兩個CSS文件下載過程中,我們看到“紅色劍靈”,“藍(lán)色劍靈”依次變?yōu)榧t色和藍(lán)色,兩者顏色的轉(zhuǎn)換時間相差在10秒,說明樣式文件和JS文件一樣是下載完一個解析一個的。
現(xiàn)在轉(zhuǎn)到TestCssOrder.aspx看一下,可以看到 開始時“紅色劍靈”,“紅色強(qiáng)壯劍靈”,顯示為紅色,過了10秒“藍(lán)色劍靈”顯示為藍(lán)色,再過10秒,“紅色強(qiáng)壯劍靈”字體變粗了,同時“紅色強(qiáng)壯劍靈 2”開始出現(xiàn)。在剛開始“紅色劍靈”,“紅色強(qiáng)壯劍靈”顯示紅色時,第三個樣式還沒有下載下來,此時IE使用已經(jīng)下載到樣式對上面的元素渲染了一遍,此時 雖然“紅色劍靈”,“紅色強(qiáng)壯劍靈”樣式定義不同,但是顯示效果一樣。第三個文件下載后,此時IE又重新對“紅色強(qiáng)壯劍靈”渲染了一遍,此時其變?yōu)榧哟郑?以上所有的文件加載并且渲染完成后,開始渲染下面的標(biāo)簽“紅色強(qiáng)壯劍靈2”
有一點(diǎn)需要證明:在IE使用樣式對標(biāo)簽進(jìn)行渲染時,是不是停止了其他頁面元素的下載?原來我想通過加長渲染時間(利用濾鏡,將標(biāo)簽元素?cái)?shù)目增大)來檢測,不過沒有驗(yàn)證成功。只是從JS函數(shù)的執(zhí)行推斷CSS的渲染也是如此。
接下來看到的是圖片文件下載,此時看到的是兩個圖片同時開始下載,而且是下載完成后,立即在頁面上開始顯示,直到所有的圖片下載完成。
注:一個測試文件在網(wǎng)絡(luò)傳輸上所花費(fèi)時間的辦法。
首先需要明白檢測中w ait值的意義:wait=服務(wù)器所花時間 + 網(wǎng)絡(luò)時間
服務(wù)器所花時間我們可以用Thread.Sleep(10000);來讓其休息10s,
比如這個:
由此大概可以計(jì)算出 10.002-10=0.002秒,這就是大概在網(wǎng)絡(luò)上所花的時間。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。