當初分析了定寬高值和定寬高比這兩種常見的圖片延遲加載場景,也介紹了他們的應對方案,還做了一點技術選型的工作。
經過一段時間的項目實踐,在先前方案的基礎上又做了很多深入的優化工作。最終將好奇心日報的網頁打開速度將降低到了1s內,Web端和端加載3屏數據消耗的流量也大幅降低。
模擬WIFI條件下的網頁加載
該篇文章結合具體的項目實踐,將圍繞如何更快的訪問網頁展開,細化到具體的技術方案,以及實踐中可能遇到的坑,希望對大家有一定的啟發和幫助。
為什么要優化網頁加載速度?
好奇心日報無論是設計還是內容都追求高品質,于是豐富的圖文混合成了標配:首頁的圖,文章詳情頁的配圖,研究所有趣的gif圖等等。
特別嚴重的時候頁面加載時顯示進度條,一篇文章有十多個gif圖,加載花費的時間10-20秒之長,加載消耗的流量幾十M之多,嚴重影響了用戶體驗!尤其是端,一寸流量一寸金;3-5s打不開頁面,用戶都會直接逃離。所以網頁加載速度優化勢在必行!
我們都知道一個網頁的加載流程大致如下:
1、解析HTML結構。
2、加載外部腳本和樣式表文件。
3、解析并執行腳本代碼。// 部分腳本會阻塞頁面的加載
4、DOM樹構建完成。// 事件
5、加載圖片等外部文件。
6、頁面加載完畢。//load 事件
一句話就是:請求HTML,然后順帶將HTML依賴的JS/CSS/等其他資源一并請求過來。
那么優化網頁的加載速度,最本質的方式就是:減少請求數量 與 減小請求大小。
減少請求數量
1、將小圖標合并成圖或者字體文件
2、用減少不必要的網絡請求
3、圖片延遲加載
4、JS/CSS按需打包
5、延遲加載ga統計
6、等等...
減小請求大小
1、JS/CSS/HTML壓縮
2、gzip壓縮
3、JS/CSS按需加載
4、圖片壓縮,jpg優化
5、webp優化 & 優化
6、等等...
JS/CSS按需打包和JS/CSS按需加載是兩個不同的概念:
JS/CSS按需打包是預編譯發生的事情,保證只打包當前頁面相關的邏輯。
JS/CSS按需加載是運行時發生的事情,保證只加載當前頁面第一時間使用到的邏輯。
接下來我們將結合兩個本質的優化方式介紹具體的實踐方法。
如何減少請求數量?
1、合并圖標,減少網絡請求
合并圖標是減少網絡請求的常見的優化手段,網頁中的小圖標特征是體積小、數量多,而瀏覽器同時發起的并行請求數量又是有限制的,所以這些小圖標會嚴重的影響網頁的加載速度,阻礙關鍵內容的請求和呈現
圖
合并圖是慢工細活兒,并沒有特別大的技術含量,但卻是每個前端開發都必須掌握的技術。
剛入門的前端直接手動切圖拼圖即可。
經驗豐富的前端可以嘗試利用構建工具實現自動化,推薦使用。gulp.插件。
// 構建視圖文件gulp.task('sprites', function() { ? ?var spriteData = gulp.src(config.src)
? ? ? ?.pipe(plumber(handleErrors))
? ? ? ?.pipe(newer(config.imgDest))
? ? ? ?.pipe(logger({ showChange: true }))
? ? ? ?.pipe(spritesmith({
? ? ? ? ? ?cssName: 'sprites.css',
? ? ? ? ? ?imgName: 'sprites.png',
? ? ? ? ? ?cssTemplate: path.resolve('./gulp/lib/template.css.handlebars')
? ? ? ?})); ? ?var imgStream = spriteData.img
? ? ? ?.pipe(buffer())
? ? ? ?.pipe(gulp.dest(config.imgDest)); ? ?var cssStream = spriteData.css
? ? ? ?.pipe(gulp.dest(config.cssDest)); ? ?return merge([imgStream, cssStream]);
});
圖不適合無線端的響應式場景,所以越來越作為一個備用方案。
字體文件
字體文件是用字體編碼的形式來實現圖標效果,既然是文字,那就可以隨意設置顏色設置大小,相對來說比方案更好。但是它只適用于純色圖標。推薦使用阿里巴巴矢量圖標庫
2、用減少不必要的網絡請求
碼兼容性
上文提到的圖和字體文件,對于有些場景并不適合,比如:
1、小背景圖,無法放到精靈圖中,通常循環平鋪小塊來設置大背景。
2、小gif圖,無法放到精靈圖中,發請求又太浪費。
使用場景
注意:壓縮css的時候,對于部分規則的 uri不能識別,會出現誤傷,如下圖,壓縮的時候會將//壓縮為/:
壓縮
原因是:會跳過data:image/data:后面的字符串,但是不會跳過data:img頁面加載時顯示進度條,所以如果你使用的工具生成的是data:img,建議換工具或者直接將其修改為data:image。
3、圖片延遲加載
圖片是網頁中流量占比最多的部分,也是需要重點優化的部分。
圖片延遲加載的原理就是先不設置img的src屬性,等合適的時機(比如滾動、滑動、出現在視窗內等)再把圖片真實url放到img的src屬性上。更多內容請移步上一篇博文:圖片延遲加載方案
固定寬高值的圖片
固定寬高值的圖片延遲加載比較簡單,因為寬高值都可以設置在css中,只需考慮src的替換問題,推薦使用。
// 引入js文件<script src="lazysizes.min.js" async="">script>// 非響應式 例子<img src="" data-src="image.jpg" class="lazyload" />// 響應式 例子,自動計算合適的圖片<img
? ?data-sizes="auto"
? ?data-src="image2.jpg"
? ?data-srcset="image1.jpg 300w,
? ?image2.jpg 600w,
? ?image3.jpg 900w" class="lazyload" />// iframe 例子<iframe frameborder="0"
? ?class="lazyload"
? ?allowfullscreen=""
? ?data-src="http://www.youtube.com/embed/ZfV-aYdU4uE">iframe>
注意:瀏覽器解析img標簽的時候,如果src屬性為空,瀏覽器會認為這個圖片是壞掉的圖,會顯示出圖片的邊框,影響市容。
第一塊是初始狀態,第四塊是成功狀態,第二塊第三塊是影響市容的狀態
延遲加載過程中會改變圖片的class:默認,加載中,加載結束:。結合這個特性我們有兩種解決上述問題辦法:
1、設置:0,然后在顯示的時候設置:1。
// 漸現 lazyload.lazyload,
.lazyloading{
? ?opacity: 0;
}
.lazyloaded{
? ?opacity: 1;
? ?transition: opacity 500ms; //加上transition就可以實現漸現的效果}
2、用一張默認的圖占位,比如1x1的透明圖或者灰圖。
<img class="lazyload"
? ?src="data:image/gif;base64,R0lGODlhAQABAAA
? ? ? AACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
? ?data-src="真實url"
? ?alt="<%= article.title %>">
此外,為了讓效果更佳,尤其是文章詳情頁中的大圖,我們可以加上效果。
.article-detail-bd {
? ?.lazyload {
? ? ? ?opacity: 0;
? ?}
? ?.lazyloading {
? ? ? ?opacity: 1;
? ? ? ?background: #f7f7f7 url(/images/loading.gif) no-repeat center;
? ?}
}
固定寬高比的圖片
固定寬高比的圖片延遲加載相對來說復雜很多,比如文章詳情頁的圖片,由于設備的寬度值不確定,所以高度值也不確定,這時候工作的重心反倒放到了如何確定圖片的高度上。
為什么要確定圖片的高度呢?因為單個圖片的加載是從上往下,所以會導致頁面抖動,不僅用戶體驗很差,而且對于性能消耗很大,因為每次抖動都會觸發(重繪)事件,之前的博文網站性能優化 之 渲染性能也分析過重繪對于性能的消耗問題。
固定寬高比的圖片抖動問題,有下列兩種主流的方式可以解決:
1、第一種方案使用-top或者-來實現固定寬高比。優點是純CSS方案,缺點是HTML冗余,并且對輸出到第三方不友好。
<div style="padding-top:75%">
? ?<img data-src="" alt="" class="lazyload"><div>
2、第二種方案在頁面初始化階段利用ratio設置實際寬高值,優點是html干凈,對輸出到第三方友好,缺點是依賴js,理論上會至少抖動一次。
"" alt="" class="lazyload" data-ratio="0.75">
那么,這個-top: 75%;和data-ratio="0.75"的數據從哪兒來呢?在你上傳圖片的時候,需要后臺給你返回原始寬高值,計算得到寬高比,然后保存到data-ratio上。
好奇心日報采用的第二種方案,主要在于第一種方案對第三方輸出不友好:需要對img設置額外的樣式,但第三方平臺通常不允許引入外部樣式。
確定第二種方案之后,我們定義了一個設置圖片高度的函數:
// 重置圖片高度,僅限文章詳情頁function resetImgHeight(els, placeholder) { ? ?var ratio = 0,
? ? ? ?i, len, width; ? ?for (i = 0, len = els.length; i < len; i++) {
? ? ? ?els[i].src = placeholder;
? ? ? ?width = els[i].clientWidth; //一定要使用clientWidth
? ? ? ?if (els[i].attributes['data-ratio']) {
? ? ? ? ? ?ratio = els[i].attributes['data-ratio'].value || 0;
? ? ? ? ? ?ratio = parseFloat(ratio);
? ? ? ?} ? ? ? ?if (ratio) {
? ? ? ? ? ?els[i].style.height = (width * ratio) + 'px';
? ? ? ?}
? ?}
}
我們將以上代碼的定義和調用都直接放到了HTML中,就為了一個目的,第一時間計算圖片的高度值,降低用戶感知到頁面抖動的可能性,保證最佳效果。
// 原生代碼""
? ?data-ratio="0.562500"
? ?data-format="jpeg"

? ?class="lazyload"
? ?data-src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
? ?src="">// 解析之后的代碼""
? ?data-ratio="0.562500"
? ?data-format="jpeg"
? ?class="lazyloaded"
? ?data-src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
? ?src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
? ?style="height: 323.438px;">
我們不僅保存了寬高比,還保存了圖片格式,是為了后期可以對gif做進一步的優化。
注意事項
1、避免圖片過早加載,把臨界值調低一點。在實際項目中,并不需要過早就把圖片請求過來,尤其是項目,過早請求不僅浪費流量,也會因為請求太多,導致頁面加載速度變慢。
2、為了最好的防抖效果,設置圖片高度的JS代碼內嵌到HTML中以便第一時間執行。
3、根據圖片寬度設置高度時,使用而不是width。這是因為中,第一時間執行的JS代碼獲取圖片的width失敗,所以使用解決這個問題。
4、JS/CSS按需打包
按需打包是獨特的優勢,如果有需要通過此種方式來管理模塊之間的依賴關系,強烈推薦引入!門檻較高,可以看看我之前的博客:
入門
模塊化機制
好奇心日報是典型的多頁應用,為了緩存通用代碼,我們使用按需打包的同時,還利用的 插件抽離出公用的JS/CSS代碼,便于緩存,在請求數量和公用代碼的緩存之間做了一個很好的平衡。
5、延遲加載ga統計async & defer屬性
html5中給標簽引入了async和defer屬性。
帶有async屬性的標簽,會在瀏覽器解析時立即下載腳本同時不阻塞后續的渲染和加載等事件,從而實現腳本的異步加載。
帶有defer屬性的標簽,和async擁有類似的功能。并且他們有可以附帶一個事件');,該方式也可以避免阻塞。
ga統計代碼
ga統計代碼采用就是動態創建標簽方案。
該方法不阻塞頁面渲染,不阻塞后續請求,但會阻塞.事件,頁面的表現方式是進度條一直加載或菊花一直轉。
所以我們延遲執行ga初始化代碼,將其放到.函數中去執行,可以防止ga腳本阻塞.事件。從而讓用戶感受到更快的加載速度。
將ga加載綁定