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