動(dòng)態(tài)網(wǎng)站開(kāi)發(fā)教案xp優(yōu)化大師
圖像優(yōu)化問(wèn)題主要可以分為兩方面:圖像的選取和使用,圖像的加載和顯示。
圖像基礎(chǔ)
HTTP Archive上的數(shù)據(jù)顯示,網(wǎng)站傳輸?shù)臄?shù)據(jù)中,60%的資源都是由各種圖像文件組成的,當(dāng)然這些是將各類型網(wǎng)站平均的結(jié)果,單獨(dú)只看電商類網(wǎng)站,這個(gè)比例可能會(huì)更大,如此之大的資源占比,同樣意味著有很大的優(yōu)化空間。
圖像是否必需
圖像資源優(yōu)化的根本思想:壓縮。無(wú)論是選取何種圖像的文件格式,還是針對(duì)于同一種格式壓縮至更小的尺寸,其本質(zhì)都是用更小的資源開(kāi)銷來(lái)完成圖像的傳輸和展示。
我們首先需要思考下要達(dá)到期望的信息傳遞效果,是否需要圖像?這不僅是因?yàn)閳D像資源與網(wǎng)頁(yè)上的其他資源(html/css/js等)相比有更大的字節(jié)開(kāi)銷,出于對(duì)節(jié)省資源的考慮,對(duì)用戶注意力的珍惜也很重要,如果一個(gè)頁(yè)面打開(kāi)之后有很多圖像,那么用戶其實(shí)很難快速梳理出有效的信息,即便獲取到了也會(huì)讓用戶覺(jué)得很累。一個(gè)低感官體驗(yàn)的網(wǎng)站,它的價(jià)值轉(zhuǎn)化率不會(huì)很高。
當(dāng)確定了圖像的展示效果必須存在時(shí),在前端實(shí)現(xiàn)上也并非一定就要用圖像文件,還存在一些場(chǎng)景可以使用更高效的方式來(lái)實(shí)現(xiàn)所需的效果。
- 網(wǎng)站中一個(gè)圖像在不同的頁(yè)面或不同的交互狀態(tài)下,需要呈現(xiàn)不同的效果(邊角的裁切、陰影或漸變),其實(shí)沒(méi)有必要準(zhǔn)備不同效果的圖像,使用css即可,相對(duì)于一張圖像文件的大小來(lái)講,修改其所增加的css代碼量忽略不計(jì)。
- 如果一個(gè)圖像上面需要顯示文字,建議通過(guò)網(wǎng)頁(yè)字體的形式通過(guò)前端代碼進(jìn)行添加,而不是使用帶文字的圖像,其原因一方面是包含了更多信息的圖像文件一般會(huì)更大,另一方面圖像中的文本信息代碼的用戶體驗(yàn)一般較差(不可選擇、搜索以及縮放),并且在高分辨率設(shè)備上的顯示效果也會(huì)大打折扣。
因此,我們?cè)谶x擇使用某種資源之前,如果期望達(dá)到更優(yōu)的性能效果,則需要先思考這種選擇是否必須。
矢量圖和位圖
當(dāng)確定了圖像是實(shí)現(xiàn)展示效果的最佳方式時(shí),接下來(lái)就是選擇合適的圖像格式。圖像文件可以分為兩類:矢量圖和位圖。
- 矢量圖
矢量圖的優(yōu)點(diǎn)時(shí)能夠在任何縮放比例下呈現(xiàn)出細(xì)節(jié)同樣清晰的展示效果。其缺點(diǎn)是對(duì)細(xì)節(jié)的展示效果不夠豐富,對(duì)足夠復(fù)雜的圖像來(lái)說(shuō),比如要達(dá)到照片的效果,若通過(guò)svg進(jìn)行矢量圖繪制,則所得文件會(huì)大的離譜,但即便如此也很難達(dá)到照片的真實(shí)效果。目前幾乎所有的瀏覽器都支持svg。
svg標(biāo)簽所包括的部分就是該矢量圖的全部?jī)?nèi)容,除了必要的繪制信息,可能還包括一些元數(shù)據(jù),比如xml命名空間、圖層以及注釋信息。但這些信息對(duì)瀏覽器繪制一個(gè)svg來(lái)說(shuō)不是必要的,所以在使用之前可通過(guò)工具去除這些元數(shù)據(jù)來(lái)達(dá)到壓縮的目的。 - 位圖
位圖是通過(guò)對(duì)一個(gè)矩陣中的柵格進(jìn)行編碼來(lái)表示圖像的,每個(gè)柵格只能編碼表示一個(gè)特定的顏色,如果組成圖像的柵格像素點(diǎn)越多且每個(gè)像素點(diǎn)所能表示的顏色范圍越廣,則位圖圖像的顯示效果就會(huì)越逼真,當(dāng)然圖像文件也就越大。雖然位圖沒(méi)有像矢量圖那種不受分辨率影響的優(yōu)秀特性,但對(duì)于復(fù)雜的照片卻能提供較為真實(shí)的細(xì)節(jié)體驗(yàn)。
分辨率
在前端開(kāi)發(fā)過(guò)程中書寫css時(shí),經(jīng)常會(huì)為圖像設(shè)置顯示所需的長(zhǎng)寬像素值,但在不同的設(shè)備屏幕上,有時(shí)候相同的圖像以及相同的設(shè)置,其渲染出來(lái)的圖像會(huì)讓人明顯感覺(jué)出清晰度有差別。產(chǎn)生這個(gè)現(xiàn)象的原因涉及兩種不同的分辨率:屏幕分辨率和圖像分辨率。
圖像分辨率展示的就是該圖像文件所包含的真實(shí)像素值,比如一個(gè)200px200px的分辨率的圖像文件,它就定義了長(zhǎng)寬各200各像素點(diǎn)的信息。設(shè)備分辨率則是顯示器屏幕所能顯示的最大像素值,比如一臺(tái)電腦的顯示器分辨率為2560px1600px。更高的設(shè)備分辨率有助于顯示更絢麗多彩的圖像,這其實(shí)很適合矢量圖的發(fā)揮,因?yàn)槠洳粫?huì)失真。而對(duì)于位圖來(lái)說(shuō),只有圖像文件包含更多的像素信息時(shí),才能更充分地利用屏幕分辨率。為了能在不同的分辨率下使項(xiàng)目中所包含的圖像都能得到恰當(dāng)?shù)恼故拘Ч???梢岳胮icture標(biāo)簽和srcset屬性提供圖像的多個(gè)變體。
<img src='photo.jpg' srcset='photo@2x.jpg,photo@3x.jpg' alt='photo'/>
除了ie和其他較低版本的瀏覽器不支持,目前主流的大部分瀏覽器都已支持img標(biāo)簽的srcset屬性。在srcset屬性中設(shè)置多種分辨率的圖像文件以及使用條件,瀏覽器在請(qǐng)求之前便會(huì)先對(duì)此進(jìn)行解析,只選擇最合適的圖像文件進(jìn)行下載,如果瀏覽器不支持,務(wù)必在src屬性中包含必要的默認(rèn)圖片。
使用picture標(biāo)簽則會(huì)在多圖像文件選擇時(shí),獲得更多的控制維度,比如屏幕方向、設(shè)備大小、屏幕分辨率等。
<picture>
<source media="(min-width:800px)" srcset="photo.jpg,photo@2x.jpg"/>
<source media="(min-width:450px)" srcset="photo.jpg,photo@2x.jpg"/>
<img src='photo.jpg'/>
</picture>
由于picture標(biāo)簽也是加入標(biāo)準(zhǔn)不久的元素標(biāo)簽,所以在使用過(guò)程中,同樣應(yīng)考慮兼容問(wèn)題。
無(wú)損和有損壓縮
壓縮時(shí)降低源文件大小的有效方式,對(duì)js代碼或網(wǎng)頁(yè)的一些腳本文件而言,壓縮掉的內(nèi)容是一些多余的空格以及不影響執(zhí)行的注釋,其目的是在不損壞正常執(zhí)行的情況下,盡量縮小源文件的大小。對(duì)圖像文件而言,由于人眼對(duì)不同顏色的敏感度存在差異,所以便可通過(guò)減少對(duì)某種顏色的編碼位數(shù)來(lái)減少文件大小,甚至還可以損失部分源文件信息,以達(dá)到近似效果,使得壓縮后的文件尺寸更小。
對(duì)于圖像壓縮,應(yīng)該是使用有損壓縮還是無(wú)損壓縮,可以簡(jiǎn)單分為兩步進(jìn)行。
- 確定業(yè)務(wù)所需要展示圖像的顏色階數(shù)、圖像顯示的分辨率以及清晰程度,當(dāng)錨定了這幾個(gè)參數(shù)的基準(zhǔn)后,如果獲取的圖像源文件的相應(yīng)參數(shù)指標(biāo)過(guò)高,便可適當(dāng)進(jìn)行有損壓縮,通過(guò)降低源文件圖像質(zhì)量的方法來(lái)降低圖像文件大小。
如果業(yè)務(wù)所要求的圖像質(zhì)量較高,便可跳過(guò)有損壓縮,直接進(jìn)入第二步無(wú)損壓縮。所以是否要進(jìn)行有損壓縮,其實(shí)是在理解了業(yè)務(wù)需求后的一個(gè)可選選項(xiàng),而非必要的。 - 當(dāng)確定了展示圖像的質(zhì)量后,便可利用無(wú)損壓縮技術(shù)盡可能降低圖像大小。無(wú)損壓縮是應(yīng)當(dāng)完成的工作環(huán)節(jié)。因此最好能通過(guò)一套完整的工程方案,自動(dòng)化執(zhí)行來(lái)避免繁瑣的人工重復(fù)工作。
CSS Sprite
雪碧圖,通過(guò)將多張小圖標(biāo)拼接成一張大圖,有效地減少http請(qǐng)求數(shù)量以達(dá)到加速顯示內(nèi)容的技術(shù)。
通常對(duì)于雪碧圖的使用場(chǎng)景應(yīng)當(dāng)滿足以下條件:首先這些圖標(biāo)不會(huì)隨用戶信息的變化而變化,它們屬于網(wǎng)站通用的靜態(tài)圖標(biāo);同時(shí)單張圖標(biāo)體積盡量小,這樣經(jīng)過(guò)拼接后其性能的提升才會(huì)比較樂(lè)觀;若加載量比較大則效果會(huì)更好。但是不建議將較大的圖片拼接成雪碧圖,因?yàn)榇髨D拼接后的單個(gè)文件體積會(huì)非常大,這樣占用網(wǎng)絡(luò)帶寬的增加與請(qǐng)求完成所耗費(fèi)時(shí)間的延長(zhǎng),會(huì)完全淹沒(méi)通過(guò)減少http請(qǐng)求次數(shù)所帶來(lái)的性能提升。
雪碧圖的使用方式十分簡(jiǎn)單:通過(guò)css的background-image屬性引入雪碧圖的url后,再使用background-position定位所需要的單個(gè)圖標(biāo)再雪碧圖上的起始位置,配合width和height屬性來(lái)鎖定具體圖標(biāo)的尺寸。
.sprite-sheet{background-image:url(https://xxxxx);background-size: 24px 600px
}.icon-1 .sprite-sheet{background-position: 0 0;height:24px;width:24px;
}.icon-2 .sprite-sheet{background-position: 0 -24px;height:24px;width:24px;
}
使用雪碧圖來(lái)提升小圖標(biāo)加載性能的歷史由來(lái)已久。在http1.x環(huán)境下,它確實(shí)能夠減少相應(yīng)的http請(qǐng)求,但需要注意當(dāng)部分圖標(biāo)變更時(shí),會(huì)導(dǎo)致已經(jīng)加載的雪碧圖緩存失效。同時(shí)在http2中,最好的方式應(yīng)該是加載單張圖像文件,因?yàn)榭梢栽谝粋€(gè)http連接上發(fā)起多次請(qǐng)求,所以對(duì)于是否使用此方法,需要考慮具體的使用環(huán)境和網(wǎng)絡(luò)設(shè)置。
圖像格式選擇建議
首先明確告訴讀者:不存在適用于任何場(chǎng)景且性能最優(yōu)的圖像使用方式。所以作為開(kāi)發(fā)者,想要網(wǎng)站性能在圖像方面達(dá)到最優(yōu),如何根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的文件格式尤其重要,圖像文件的使用策略如下圖所示:
注意:使用webp格式的圖像需要考慮到瀏覽器的兼容性。通常的處理思路分為兩種:一種是在前端處理瀏覽器兼容性的判斷,可以通過(guò)瀏覽器的全局屬性window.navigator.userAgent獲取版本信息,再根據(jù)兼容支持情況,選擇是否請(qǐng)求webp圖像格式的資源;也可以使用<picture/>標(biāo)簽來(lái)選擇顯示的圖像格式。
<picture><source srcset="photo.webp" type="image/webp"/><img src='photo.jpg'/>
</picture>
除此之外,位圖對(duì)于不同縮放比的響應(yīng)式場(chǎng)景,建議提供多張不同尺寸的圖像,讓瀏覽器根據(jù)具體場(chǎng)景進(jìn)行請(qǐng)求調(diào)用。
參考文章:https://blog.csdn.net/qq_42691298/article/details/128485051
Web字體
使用web字體有多種優(yōu)點(diǎn):增強(qiáng)網(wǎng)站的設(shè)計(jì)感、可讀性,同時(shí)還能搜索和選取所表示的文本內(nèi)容,且不受屏幕尺寸與分辨率的影響,能提供一致的視覺(jué)體驗(yàn)。除此之外,由于每個(gè)字型都是特定的矢量圖標(biāo),所以可以將項(xiàng)目中用到的矢量圖打包到一個(gè)web字體文件中使用,以節(jié)省對(duì)圖標(biāo)資源的http請(qǐng)求次數(shù),這樣做類似雪碧圖優(yōu)化目的。
-
字體的使用
目前網(wǎng)絡(luò)上常用的字體格式有:EOT、TTF、WOFF與WOFF2,由于存在兼容問(wèn)題,并沒(méi)有哪一種字體能夠適用所有瀏覽器,所以在實(shí)際使用中,網(wǎng)站開(kāi)發(fā)者會(huì)聲明提供字體的多種文件格式,來(lái)達(dá)到一致性的體驗(yàn)效果。
在web項(xiàng)目中,一般會(huì)先通過(guò)@font-face聲明使用的字體系列:@font-face {font-family: 'myFont';src: url('./my-font.ttf') format('truetype');src: url('./my-font.woff') format('woff'),url('./my-font.woff2') format('woff2');font-weight: 600;font-style: normal; }.my-font {font-family: 'myFont';color: red; }
在上述代碼中通過(guò)src字段的屬性值,可以指定字體資源的位置,并且該屬性值還可以提供一個(gè)用逗號(hào)分隔的列表,列表中不同字體文件格式的資源順序同樣重要,瀏覽器將選取其所支持的第一個(gè)格式資源。如果希望較新的woff2格式被使用,則應(yīng)當(dāng)將woff2聲明在woff之上。
-
子集內(nèi)嵌
如果將所有字型都打包成一個(gè)文件來(lái)請(qǐng)求使用,不免就會(huì)存在許多根本用不到的字型信息浪費(fèi)帶寬。相較于拉丁文字體而言,包含中文字符的字體文件的大小會(huì)格外突出??梢允褂胾nicode-range屬性定義所使用的字體子集。它支持三種形式:單一取值(如U+233)、范圍取值(如U+233-2ff)、通配符范圍(如U+2??),取值的含義是字體集文件中的代碼索引點(diǎn):@font-face {font-family: 'myFont';src: url('./my-font.ttf') format('truetype');src: url('./my-font.woff') format('woff'),url('./my-font.woff2') format('woff2');unicode-range:U+100-3ff,U+f??;font-weight: 600;font-style: normal; }
通過(guò)使用子集內(nèi)嵌,以及為字體的不同樣式變體采用單獨(dú)的文件,用戶可以僅根據(jù)需要下載字體的子集,而不必強(qiáng)制他們下載可能永遠(yuǎn)都不會(huì)用到的字體子集。不過(guò)屬性u(píng)nicode-range也存在兼容問(wèn)題,對(duì)于不支持的瀏覽器,可能需要手動(dòng)處理字體文件。
-
字體文件預(yù)加載
在默認(rèn)情況下,構(gòu)建渲染樹(shù)之前會(huì)阻塞字體文件的請(qǐng)求,這將可能導(dǎo)致部分文本渲染延遲,對(duì)此可使用<link rel=“preload”>對(duì)字體資源進(jìn)行加載。
<head>
<link rel="preload" href="xxxx" as ="font"/>
</head>
link需要和@font-face對(duì)字體的定義一同使用,它只負(fù)責(zé)提示瀏覽器需要預(yù)加載給定的資源,而不指明如何使用。需要注意的是,這樣做將會(huì)無(wú)條件向網(wǎng)絡(luò)發(fā)出字體請(qǐng)求如果項(xiàng)目迭代將原本使用的字體文件修改或刪除,也需要同步刪除對(duì)字體預(yù)加載的設(shè)置。
參考文章:https://blog.csdn.net/weixin_46820017/article/details/116666903
注意display:none的使用
在使用位圖時(shí),經(jīng)常會(huì)根據(jù)屏幕尺寸,權(quán)限控制等不同條件,響應(yīng)式地處理資源的展示與隱藏。出于對(duì)性能的考慮,希望對(duì)于不展示的圖像,盡量避免在首屏?xí)r進(jìn)行資源請(qǐng)求加載。
<div style="display:none"><img src="img.png"/>
</div>
根據(jù)html的解析順序,img.png的圖像文件會(huì)被請(qǐng)求。
<div style="display:none"><div style="background:url(img.png)"/>
</div>
css解析后發(fā)現(xiàn)父級(jí)使用了none,再去計(jì)算子級(jí)的樣式就沒(méi)有多大意義了,所以就不會(huì)去下載子級(jí)div的背景圖片。
如果不清楚不同瀏覽器對(duì)display:none關(guān)于圖像加載的控制,則可以通過(guò)開(kāi)發(fā)者工具進(jìn)行驗(yàn)證。
筆者這里推薦的做法是使用<picture/>或<img srcset>的方式進(jìn)行響應(yīng)式顯示。
圖像延遲加載
在首次打開(kāi)網(wǎng)站時(shí),應(yīng)盡量只加載首屏內(nèi)容所包含的資源,而首屏之外涉及的圖片或視頻,可以等到用戶滾動(dòng)視窗瀏覽時(shí)再去加載。
實(shí)現(xiàn)圖片的延遲加載:傳統(tǒng)方式
通過(guò)監(jiān)聽(tīng)的方式,通過(guò)監(jiān)聽(tīng)scroll事件與resize事件,并在事件的回調(diào)函數(shù)中去判斷,需要進(jìn)行延遲加載的圖片是否進(jìn)入視窗區(qū)域。
首先定義出將要實(shí)現(xiàn)延遲加載的<img>標(biāo)簽結(jié)構(gòu):
<img class="lazy" alt="" src="xxxx" data-src="xxxx"/>
- src屬性,加載前的占位符圖片,可用base64圖片或低分辨率的圖片。
- data-src屬性,通過(guò)該自定義屬性保存圖片真實(shí)的url外鏈。
對(duì)于只可上下滾動(dòng)的頁(yè)面,判斷一個(gè)圖片元素是否出現(xiàn)在屏幕視窗中的方法其實(shí)顯而易見(jiàn),即當(dāng)元素上邊緣距屏幕視窗頂部的top值小于整個(gè)視窗的高度window.innerHeight時(shí),預(yù)加載的事件處理代碼如下:
//在dom內(nèi)容加載完畢后,執(zhí)行延遲加載處理邏輯
document.addEventListener("DOMContentLoaded",function(){//獲取所有需要延遲加載的圖片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//限制函數(shù)頻繁被調(diào)用let active= false;const lazyLoad = function(){if(active==false){active = true;setTimeout(function(){lazyImages.forEach(function(lazyImage){//判斷圖片是否出現(xiàn)在視窗中if((lazyImage.getBoundingClientRect().top<=window.innerHeight&&lazyImage.getBoundingClientRect().bottom>=0)&&getComputedStyle(lazyImage).display!=='none'){//將真實(shí)的圖片url賦值給src屬性,發(fā)起請(qǐng)求加載資源lazyImage.src = lazyImage.dataset.src;lazyImage.classList.remove("lazy");lazyImages = lazyImages.filter(function(image){return image !== lazyImage;})//所有延遲加載圖片加載完成后,移除事件觸發(fā)處理函數(shù)if(lazyImages.length===0){document.removeEventListener("scroll",lazyLoad);document.removeEventListener("resize",lazyLoad);document.removeEventListener("orientationchange",lazyLoad);}}});active=false;},200)}};document.addEventListener("scroll",lazyLoad);document.addEventListener("resize",lazyLoad);document.addEventListener("orientationchange",lazyLoad);
})
由于無(wú)法控制用戶隨心所欲地滑動(dòng)鼠標(biāo)滾輪,從而造成scroll事件被觸發(fā)地過(guò)于頻繁,導(dǎo)致過(guò)多的冗余計(jì)算影響性能。所以通過(guò)active標(biāo)志位的方式進(jìn)行限流。
即便如此也有潛在的性能問(wèn)題,因?yàn)橹貜?fù)的setTimeout調(diào)用時(shí)浪費(fèi)的,雖然進(jìn)行了觸發(fā)限制,但當(dāng)文檔滾動(dòng)或窗口大小調(diào)整時(shí),不論圖片是否出現(xiàn)在視窗中,每200ms都會(huì)執(zhí)行一次檢查,并且跟蹤尚未加載的圖片數(shù)量,以及完全加載完后,取消綁定滾動(dòng)事件的處理函數(shù)等操作都需要開(kāi)發(fā)者來(lái)考慮。
如此看來(lái),雖然傳統(tǒng)的延遲加載方式具有良好的瀏覽器兼容性,但是實(shí)現(xiàn)起來(lái)比較瑣碎。
實(shí)現(xiàn)圖片的延遲加載:IntersectionObserver方式
現(xiàn)代瀏覽器大多支持了IntersectionObserver API,可以通過(guò)它來(lái)檢查目標(biāo)元素的可見(jiàn)性,這種方式的性能和效率都比較好。
IntersectionObserver:每當(dāng)因頁(yè)面滾動(dòng)或窗口尺寸發(fā)生變化,使得目標(biāo)元素與設(shè)備視窗或其他指定元素產(chǎn)生交集時(shí),便會(huì)觸發(fā)通過(guò)IntersectionObserver API配置的回調(diào)函數(shù),在該函數(shù)中進(jìn)行延遲加載的邏輯處理,會(huì)比傳統(tǒng)方式顯得更加簡(jiǎn)潔而高效。
//在dom內(nèi)容加載完畢后,執(zhí)行延遲加載處理邏輯
document.addEventListener("DOMContentLoaded",function(){//獲取所有需要延遲加載的圖片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//判斷瀏覽器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判斷圖片是否出現(xiàn)在視窗中if(entry.isIntersecting){let lazyImage = entry.target;lazyImage.src = lazyImage.dataset.src;lazyImage.classList.remove("lazy");lazyImageObserver.unobserver(lazyImage);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
這種方式判斷元素是否出現(xiàn)在視窗中更為簡(jiǎn)單直觀,應(yīng)在實(shí)際開(kāi)發(fā)中盡量使用。但其問(wèn)題是并非所有瀏覽器都能兼容。在將這種方式引入項(xiàng)目之前,應(yīng)當(dāng)確保已做到以下兩點(diǎn):
- 做好盡量完備瀏覽器兼容性檢查,對(duì)于兼容IntersectionObserver API的瀏覽器,采取這種方式處理,而對(duì)于不兼容的瀏覽器,則使用傳統(tǒng)的實(shí)現(xiàn)方式。
- 使用相應(yīng)兼容的polyfill插件。
實(shí)現(xiàn)圖片的延遲加載:CSS類名方式
這種方式通過(guò)css的background-image屬性來(lái)加載圖片,與判斷<img>標(biāo)簽的src屬性是否有要請(qǐng)求圖片的url不同,css中圖片加載的行為建立在瀏覽器對(duì)文檔分析基礎(chǔ)之上。
具體來(lái)說(shuō),當(dāng)dom樹(shù),cssom樹(shù)以及渲染樹(shù)生成后,瀏覽器會(huì)去檢查css以何種方式應(yīng)用于文檔,再?zèng)Q定是否請(qǐng)求外部資源。如果瀏覽器確定涉及外部資源請(qǐng)求的css規(guī)則再當(dāng)前文檔中不存在,便不會(huì)去請(qǐng)求該資源。
<div class="wrap"><div class="lazy-background one"/><div class="lazy-background two"/><div class="lazy-background three"/>
</div>
具體的實(shí)現(xiàn)方式是通過(guò)js來(lái)判斷元素是否出現(xiàn)在視窗中,當(dāng)在視窗中時(shí),為其元素的class屬性添加visible類名。而在css文件中,為同一類名元素定義出帶.visible和不帶.visible的兩種包含background-image規(guī)則。
不帶.visible的圖片規(guī)則中的background-image屬性可以是低分辨率的圖片或base64圖片,而帶.visible的圖片規(guī)則中的background-image屬性為希望展示的真實(shí)圖片的url。
//在dom內(nèi)容加載完畢后,執(zhí)行延遲加載處理邏輯
document.addEventListener("DOMContentLoaded",function(){//獲取所有需要延遲加載的圖片let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));//判斷瀏覽器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判斷圖片是否出現(xiàn)在視窗中if(entry.isIntersecting){entry.target.classList.add("visible")lazyImageObserver.unobserver(entry.target);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
原生的延遲加載支持
除了上述通過(guò)開(kāi)發(fā)者手動(dòng)實(shí)現(xiàn)延遲加載邏輯的方式,從chrome75版本開(kāi)始,已經(jīng)可以通過(guò)<img>和<iframe>標(biāo)簽的loading屬性原生支持延遲加載了,loading屬性包含了以下三種取值。
- lazy:進(jìn)行延遲加載
- eager:立即加載
- auto:瀏覽器自行決定是否進(jìn)行延遲加載(默認(rèn)值)
兼容性處理:通過(guò)使用新技術(shù)優(yōu)化了延遲加載的實(shí)現(xiàn)方式,同時(shí)也應(yīng)該注意新技術(shù)在不同瀏覽器之間的兼容性,在使用前需要對(duì)瀏覽器特性進(jìn)行檢查。
<script>
if("loading" in HTMLImageElement.prototype){//瀏覽器支持loading的延遲加載方式
}else{//獲取其他js庫(kù)來(lái)實(shí)現(xiàn)延遲加載
}
</script>
視頻加載
不需要自動(dòng)播放
由于chrome等一些瀏覽器會(huì)對(duì)視頻資源進(jìn)行預(yù)加載,即在html完成加載和解析時(shí)觸發(fā)DOMContentLoaded事件開(kāi)始請(qǐng)求視頻資源,當(dāng)請(qǐng)求完成后觸發(fā)window.onload事件開(kāi)始頁(yè)面渲染。
為了使頁(yè)面更快地加載并渲染出來(lái),可以阻止不需要自動(dòng)播放的視頻的預(yù)加載。其方法是通過(guò)視頻標(biāo)簽的preload進(jìn)行控制:
<video preload="none" poster="default.png"><source src="simply.webm" type="video/webm" /><source src="simply.mp4" type="video/mp4" />
</video>
preload屬性通常的默認(rèn)值是auto,表示無(wú)論用戶是否希望,所有視頻文件都會(huì)被自動(dòng)下載,這里將其設(shè)置為none,來(lái)阻止視頻的自動(dòng)預(yù)加載。同時(shí)還通過(guò)poster屬性為視頻提供占位符圖片。
- chrome之前的版本中,preload的默認(rèn)值是auto,從64版本以后其默認(rèn)值改為了metadata,表示僅加載視頻的元數(shù)據(jù),火狐、ie11和edge等瀏覽器的行為類似。
- safari11.0的mac版本會(huì)默認(rèn)進(jìn)行部分視頻資源預(yù)加載,11.2的mac版后僅可預(yù)加載元數(shù)據(jù),但ios的safari不會(huì)對(duì)視頻預(yù)加載。
- 若瀏覽器開(kāi)啟了省流量模式,preload將默認(rèn)值設(shè)置為none。
當(dāng)瀏覽器支持preload的metadata屬性值后,這將會(huì)是一種兼顧了性能和體驗(yàn)后更優(yōu)的方式,因?yàn)閺捏w驗(yàn)上說(shuō),對(duì)于不自動(dòng)播放的視頻場(chǎng)景,在單擊播放前,若能提前告知視頻的播放時(shí)長(zhǎng)、播放列表等元數(shù)據(jù),便能帶來(lái)更好的用戶體驗(yàn)。
另外,如果站點(diǎn)中包含了同一域名下的多個(gè)視頻資源,那么最好將preload設(shè)置為metadata,或者定義poster屬性值時(shí)將preload設(shè)置為none,這樣可以很好地避免http地最大連接數(shù),因?yàn)橥ǔttp1.1協(xié)議規(guī)定同一域名下地最大連接數(shù)為6,如果同時(shí)有超過(guò)此數(shù)量地資源請(qǐng)求連接,那么多余地連接便會(huì)被掛起,這無(wú)疑會(huì)對(duì)性能造成負(fù)面影響。
視頻代替GIF動(dòng)畫
應(yīng)當(dāng)盡量用視頻代替尺寸過(guò)大地gif動(dòng)畫。雖然gif動(dòng)畫的應(yīng)用歷史和范圍都很廣泛,但其在輸出文件大小、圖像色彩質(zhì)量等許多方面的表現(xiàn)都不如視頻。gif動(dòng)畫相對(duì)于視頻具有三個(gè)附加的特性:沒(méi)有音軌、連續(xù)循環(huán)播放、加載完自動(dòng)播放,替換成視頻后類似于:
<video autoplay muted loop playsinline width="610" height="254" poster="default.png"><source src="simply.webm" type="video/webm" /><source src="simply.mp4" type="video/mp4" />
</video>
autoplay自動(dòng)播放、muted靜音播放以及l(fā)oop循環(huán)播放,而playsinline屬性則用于在ios中指定自動(dòng)播放的。注意并非所有瀏覽器都像chrome一樣,能夠自動(dòng)進(jìn)行延遲加載,因此我們可以根據(jù)上面的提及到的IntersectionObserver的方式實(shí)現(xiàn)延遲加載的控制。
//在dom內(nèi)容加載完畢后,執(zhí)行延遲加載處理邏輯
document.addEventListener("DOMContentLoaded",function(){//獲取所有需要延遲加載的圖片let lazyImages = [].slice.call(document.querySelectorAll("video.lazy"));//判斷瀏覽器兼容性if("IntersectionObserver" in window && "IntersectionObserverEntry" in window && "intersectionRatio" in window.IntersectionObserverEntry.prototype){let lazyImageObserver = new IntersectionObserver(function (entries,observer){entries.forEach(function(entry){//判斷圖片是否出現(xiàn)在視窗中if(entry.isIntersecting){for(const source in entry.target,children){const videoSrc = entry.target.children[source];if(typeof videoSrc.tagName==='string' && videoSrc.tagName==='source'){videoSrc.src = videoSrc.dataset.src;}}entry.target.load();//需要手動(dòng)調(diào)用load方法entry.target.classList.remove("lazy")lazyImageObserver.unobserver(entry.target);}})})lazyImages.forEach(function(lazyImage){lazyImageObserver.observer(lazyImage)})}
})
加載注意事項(xiàng)
首屏加載
對(duì)于首屏上的內(nèi)容就不應(yīng)當(dāng)進(jìn)行延遲加載,而應(yīng)該正常加載。這樣處理的原因是:延遲加載會(huì)將圖像或視頻等媒體資源延遲到dom可交互之后,即腳本完成加載并開(kāi)始執(zhí)行時(shí)才會(huì)進(jìn)行。所以對(duì)首屏視窗之外的媒體資源采用延遲加載,而對(duì)首屏視窗內(nèi)的媒體資源采用正常的方式加載,會(huì)帶來(lái)更好的性能體驗(yàn)。
由于網(wǎng)站頁(yè)面所呈現(xiàn)的設(shè)備屏幕尺寸多種多樣,因此如何判斷首屏視窗的邊界就會(huì)因設(shè)備的不同而有所不同。目前也沒(méi)有完全行之有效的方法來(lái)完美地處理每種設(shè)備的情況。
此外,若將首屏視窗邊界線作為延遲加載觸發(fā)的閾值,其實(shí)并非最佳的性能考慮。更為理想的做法是,在延遲加載的媒體資源到達(dá)首屏邊界之前設(shè)置一個(gè)緩沖區(qū),以便媒體資源在進(jìn)入視窗之前就開(kāi)始進(jìn)行加載。
let lazyImageObserver = new IntersectionObserver(function (entries,observer){
},{rootMargin:"0 0 256px 0"})//當(dāng)媒體元素距離視窗下邊界小于256px時(shí),回調(diào)函數(shù)會(huì)執(zhí)行
內(nèi)容加載失敗
const newImage = new Image();
newImage.src="photo.jpg"
//當(dāng)發(fā)生故障時(shí)的處理措施
newImage.onerror=(err)=>{
}
//圖像加載后的回調(diào)
newImage.onload=()=>{
}
當(dāng)圖片資源未能按預(yù)期成功加載時(shí),所采取的具體處理措施應(yīng)當(dāng)依據(jù)應(yīng)用場(chǎng)景而定。比如,當(dāng)請(qǐng)求的媒體資源無(wú)法加載時(shí),可將使用的圖像占位符替換成按鈕,或者在占位符區(qū)域顯示錯(cuò)誤的提示信息。
圖像解碼延遲
圖像從被瀏覽器請(qǐng)求獲取,再到最終完整呈現(xiàn)在屏幕上,需要經(jīng)歷一個(gè)解碼的過(guò)程,圖像的尺寸越大,所需要的解碼時(shí)間就越長(zhǎng)。如果在js中請(qǐng)求加載較大的圖像文件,并把它直接放入dom結(jié)構(gòu)中后,那么將可能占用瀏覽器的主進(jìn)程,進(jìn)而導(dǎo)致解碼期間用戶界面出現(xiàn)短暫的無(wú)響應(yīng)。
<button id="load-image">加載圖像
</button>
<div id="image-container"/>
對(duì)于的js事件處理代碼如下:
document.addEventListener("DOMCOntentLoaded",()=>{const loadButton = document.getElementById("load-image");const imageContainer = document.getElementById("image-container");const newImage = new Image();newImage.src = "https://sssss"loadButton.addEventListener("click",function(){if("decode" in newImage){//異步解碼方式newImage.decode().then(()=>{imageContainer.appendChild(newImage);})}else{//正常圖像加載方式imageContainer.appendChild(newImage);}},{once:true})
})
需要說(shuō)明的是,如果網(wǎng)站所包含的大部分圖像尺寸都很小,那么使用這種方式的幫助并不大,同時(shí)還會(huì)增加代碼的復(fù)雜性,但可以肯定的是這么做會(huì)減少延遲加載大型圖像文件所帶來(lái)的卡頓。
js是否可用
在通常情況下,js都是始終可用的,但在一些異常不可用的情況下,開(kāi)發(fā)者應(yīng)當(dāng)做好適配,不能始終在延遲加載的圖像位置上展示占位符??梢钥紤]使用<noscript>標(biāo)記,在js不可用時(shí)提供圖像的真實(shí)展示:
<!-- 使用延遲加載的圖像文件標(biāo)簽 -->
<img class = "lazy" src="aaa.png" data-src = "load.png" alt="xxx"/>
<!-- js不可用時(shí),原生展示目標(biāo)圖像 -->
<noscript><img src="load.png" alt="xxxx"/>
</noscript>
如果上述代碼同時(shí)存在,當(dāng)js不可用時(shí),頁(yè)面中會(huì)同時(shí)展示圖像占位符和<noscript>中包含的圖像,為此可以給<html>標(biāo)簽添加一個(gè)no-js類:
<html class="no-js">
在由<link>標(biāo)簽請(qǐng)求css文件之前,在<head>標(biāo)簽結(jié)構(gòu)中放置一段內(nèi)聯(lián)腳本,當(dāng)js可用時(shí),用于移除no-js類:
<script>document.documentElement.classList.remove("no-js")
</script>
以及添加必要的css樣式,使得在js不可用時(shí)屏蔽包含.lazy類元素的顯示:
.no-js .lazy{display:none;
}
資源優(yōu)先級(jí)
瀏覽器向網(wǎng)絡(luò)請(qǐng)求到所有數(shù)據(jù),并非每個(gè)字節(jié)都具有相同的優(yōu)先級(jí)或重要性。所以瀏覽器通常都會(huì)采取啟發(fā)式算法,對(duì)所要加載的內(nèi)容先進(jìn)行推測(cè),將相對(duì)重要的信息優(yōu)先呈現(xiàn)給用戶。比如瀏覽器一般會(huì)先加載css文件,然后再去加載js腳本和圖像文件。
但即便如此,也無(wú)法保證啟發(fā)式算法在任何情況下都是準(zhǔn)確有效的。
預(yù)加載
使用<link rel=“preload”>標(biāo)簽告訴瀏覽器當(dāng)前所指定的資源,應(yīng)該擁有更高的優(yōu)先級(jí),例如:
<link rel="preload" as="script" href="test.js"/><link rel="preload" as="style" href="test.css"/><!-- 通常字體文件都位于頁(yè)面加載的若干css文件的末尾,當(dāng)為了減少用戶等待文本內(nèi)容的時(shí)間,就必須要提取獲取字體 --><link rel="preload" as="font" type="font/woff2" crossorigin="crossorigin" href="test.woff2"/>
這里通過(guò)as屬性告訴瀏覽器所要加載的資源類型,該屬性值所指定的資源類型應(yīng)該與要加載的資源相匹配,否則瀏覽器是不會(huì)預(yù)加載該資源的。在這里需要注意的是,<link rel=“preload”>會(huì)強(qiáng)制瀏覽器進(jìn)行預(yù)加載,它與其他對(duì)資源的提示不同,瀏覽器對(duì)此是必須執(zhí)行的。因此,在使用時(shí)應(yīng)該盡量仔細(xì)測(cè)試,以確保使用該指令時(shí)不會(huì)提取不需要的內(nèi)容或重復(fù)的內(nèi)容。
如果預(yù)加載指定的資源在3s內(nèi)未被當(dāng)前頁(yè)面使用,則瀏覽器會(huì)在開(kāi)發(fā)者工具的控制臺(tái)中進(jìn)行告警提示,該告警提示務(wù)必要處理。
預(yù)連接
通常在速度較慢的網(wǎng)絡(luò)環(huán)境中建立連接會(huì)非常耗時(shí),如果建立安全連接將更加耗時(shí)。其原因是整個(gè)過(guò)程會(huì)涉及到dns查詢,重定向和與目標(biāo)服務(wù)器之間建立連接的多次握手,所以若能提前完成上述這些功能,則會(huì)給用戶帶來(lái)更加流暢的瀏覽體驗(yàn),同時(shí)由于建立連接的大部分時(shí)間消耗是等待而非交換數(shù)據(jù),這樣也能有效地優(yōu)化帶寬的使用情況。
<link rel="preconnect" href="https://www.xxxx"/>
通過(guò)該指令,告訴瀏覽器當(dāng)前頁(yè)面將與站點(diǎn)建立連接,希望盡快啟動(dòng)該過(guò)程。雖然這么做的成本較低,但會(huì)消耗寶貴的cpu時(shí)間,特別是在建立https安全連接時(shí)。如果建立好連接后的10s內(nèi),未能及時(shí)使用連接,那么瀏覽器關(guān)閉該連接后,之前未建立連接所消耗的資源相當(dāng)于完全浪費(fèi)掉了。
另外,還有一種與預(yù)連接相關(guān)的類型<link rel=“dns-prefetch”>,也就是常說(shuō)的dns預(yù)解析,它僅用來(lái)處理dns查詢,但由于其受到瀏覽器的廣泛支持,且縮短了dns的查詢時(shí)間的效果顯著,所以使用場(chǎng)景十分普遍。
預(yù)提取
前面介紹的預(yù)加載和預(yù)連接,都是試圖使所需的關(guān)鍵資源或關(guān)鍵操作更快地獲取或發(fā)生,這里介紹地預(yù)提取,則是利用機(jī)會(huì)讓某些非關(guān)鍵操作能夠更早發(fā)生。
這個(gè)過(guò)程的實(shí)現(xiàn)方式是根據(jù)用戶已經(jīng)發(fā)生的行為來(lái)判斷其接下來(lái)的預(yù)期行為,告訴瀏覽器稍后可能需要的某些資源。也就是在當(dāng)前頁(yè)面加載完成后,且在寬帶可用的情況下,這些資源將以Lowest的優(yōu)先級(jí)進(jìn)行提起。
顯而易見(jiàn),預(yù)提取最合適的場(chǎng)景是為用戶下一步可能進(jìn)行的操作做好必要的準(zhǔn)備,如在電商平臺(tái)的搜索框中查詢商品,可預(yù)提取查詢結(jié)果列表中的首個(gè)商品詳情頁(yè);或者使用搜索查詢時(shí),預(yù)提取查詢結(jié)果的分頁(yè)內(nèi)容的下一頁(yè):
<link rel="prefetch" href="page-2.html"/>
需要注意的是,預(yù)提取不能遞歸使用,比如在搜索查詢的首頁(yè)page-1.html時(shí),可以預(yù)提取當(dāng)前頁(yè)面的下一頁(yè)page-2.html的html內(nèi)容,但對(duì)其中所包含的任何額外資源不會(huì)提前下載,除非有額外明確指定的預(yù)提取。
另外,預(yù)提取不會(huì)降低現(xiàn)有資源的優(yōu)先級(jí),比如在如下html中:
<html><head><link rel="prefetch" href="style.css"/><link rel="stylesheet" href="style.css"/> </head>
</html>
可能讀者會(huì)覺(jué)得對(duì)style.css的預(yù)提取聲明,會(huì)降低接下來(lái)<link rel=“stylesheet”>的優(yōu)先級(jí),當(dāng)其實(shí)的情況是,該文件會(huì)被提取兩次,第二次可能會(huì)使用緩存。顯然兩次提取對(duì)用戶體驗(yàn)來(lái)說(shuō)時(shí)非常糟糕的,因?yàn)檫@樣不但需要等待阻塞渲染的css,而且如果第二次提取沒(méi)有命中緩存,必然會(huì)產(chǎn)生帶寬的浪費(fèi),所以在使用時(shí)應(yīng)充分考慮。