完整攻略!讓你的網(wǎng)頁加載時(shí)間降低到 1s 內(nèi)!

原創(chuàng)文章,未經(jīng)允許,嚴(yán)禁轉(zhuǎn)載

還記得 圖片延遲加載方案 那篇博文嗎?當(dāng)初分析了定寬高值定寬高比這兩種常見的圖片延遲加載場景,也介紹了他們的應(yīng)對方案,還做了一點(diǎn)技術(shù)選型的工作。

經(jīng)過一段時(shí)間的項(xiàng)目實(shí)踐,在先前方案的基礎(chǔ)上又做了很多深入的優(yōu)化工作。最終將好奇心日報(bào)的網(wǎng)頁打開速度將降低到了1s內(nèi),Web端和Mobile端加載3屏數(shù)據(jù)消耗的流量也大幅降低。


模擬WIFI條件下的網(wǎng)頁加載

該篇文章結(jié)合具體的項(xiàng)目實(shí)踐,將圍繞如何更快的訪問網(wǎng)頁展開,細(xì)化到具體的技術(shù)方案,以及實(shí)踐中可能遇到的坑,希望對大家有一定的啟發(fā)和幫助。

為什么要優(yōu)化網(wǎng)頁加載速度?

好奇心日報(bào)無論是設(shè)計(jì)還是內(nèi)容都追求高品質(zhì),于是豐富的圖文混合成了標(biāo)配:首頁的banner圖,文章詳情頁的配圖,研究所有趣的gif圖等等。
特別嚴(yán)重的時(shí)候,一篇文章有十多個(gè)gif圖,加載花費(fèi)的時(shí)間10-20秒之長,加載消耗的流量幾十M之多,嚴(yán)重影響了用戶體驗(yàn)!尤其是Mobile端,一寸流量一寸金;3-5s打不開頁面,用戶都會(huì)直接逃離。所以網(wǎng)頁加載速度優(yōu)化勢在必行!

我們都知道一個(gè)網(wǎng)頁的加載流程大致如下:
1、解析HTML結(jié)構(gòu)。
2、加載外部腳本和樣式表文件。
3、解析并執(zhí)行腳本代碼。// 部分腳本會(huì)阻塞頁面的加載
4、DOM樹構(gòu)建完成。//DOMContentLoaded 事件
5、加載圖片等外部文件。
6、頁面加載完畢。//load 事件
一句話就是:請求HTML,然后順帶將HTML依賴的JS/CSS/iconfont等其他資源一并請求過來。
那么優(yōu)化網(wǎng)頁的加載速度,最本質(zhì)的方式就是:減少請求數(shù)量 與 減小請求大小。

減少請求數(shù)量

1、將小圖標(biāo)合并成sprite圖或者iconfont字體文件
2、用base64減少不必要的網(wǎng)絡(luò)請求
3、圖片延遲加載
4、JS/CSS按需打包
5、延遲加載ga統(tǒng)計(jì)
6、等等...

減小請求大小

1、JS/CSS/HTML壓縮
2、gzip壓縮
3、JS/CSS按需加載
4、圖片壓縮,jpg優(yōu)化
5、webp優(yōu)化 & srcset優(yōu)化
6、等等...

JS/CSS按需打包JS/CSS按需加載是兩個(gè)不同的概念:
JS/CSS按需打包是預(yù)編譯發(fā)生的事情,保證只打包當(dāng)前頁面相關(guān)的邏輯。
JS/CSS按需加載是運(yùn)行時(shí)發(fā)生的事情,保證只加載當(dāng)前頁面第一時(shí)間使用到的邏輯。

接下來我們將結(jié)合兩個(gè)本質(zhì)的優(yōu)化方式介紹具體的實(shí)踐方法。

如何減少請求數(shù)量?

1、合并圖標(biāo),減少網(wǎng)絡(luò)請求

合并圖標(biāo)是減少網(wǎng)絡(luò)請求的常見的優(yōu)化手段,網(wǎng)頁中的小圖標(biāo)特征是體積小、數(shù)量多,而瀏覽器同時(shí)發(fā)起的并行請求數(shù)量又是有限制的,所以這些小圖標(biāo)會(huì)嚴(yán)重的影響網(wǎng)頁的加載速度,阻礙關(guān)鍵內(nèi)容的請求和呈現(xiàn)

sprite圖

合并sprite圖是慢工細(xì)活兒,并沒有特別大的技術(shù)含量,但卻是每個(gè)前端開發(fā)都必須掌握的技術(shù)。
剛?cè)腴T的前端直接手動(dòng)切圖拼圖即可。
經(jīng)驗(yàn)豐富的前端可以嘗試?yán)脴?gòu)建工具實(shí)現(xiàn)自動(dòng)化,推薦使用。gulp.spritesmith插件

// 構(gòu)建視圖文件
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]);
});

sprite圖不適合無線端的響應(yīng)式場景,所以越來越作為一個(gè)備用方案。

iconfont字體文件

iconfont字體文件是用字體編碼的形式來實(shí)現(xiàn)圖標(biāo)效果,既然是文字,那就可以隨意設(shè)置顏色設(shè)置大小,相對來說比sprite方案更好。但是它只適用于純色圖標(biāo)。推薦使用 阿里巴巴矢量圖標(biāo)庫

2、用base64減少不必要的網(wǎng)絡(luò)請求

base64碼兼容性

上文提到的sprite圖和iconfont字體文件,對于有些場景并不適合,比如:
1、小背景圖,無法放到精靈圖中,通常循環(huán)平鋪小塊來設(shè)置大背景。
2、小gif圖,無法放到精靈圖中,發(fā)請求又太浪費(fèi)。


base64使用場景

注意:cssnano壓縮css的時(shí)候,對于部分規(guī)則的base64 uri不能識(shí)別,會(huì)出現(xiàn)誤傷,如下圖,cssnano壓縮的時(shí)候會(huì)將//壓縮為/

cssnano壓縮base64

原因是:cssnano會(huì)跳過data:image/data:application后面的字符串,但是不會(huì)跳過data:img,所以如果你使用的工具生成的是data:img,建議換工具或者直接將其修改為data:image

3、圖片延遲加載

圖片是網(wǎng)頁中流量占比最多的部分,也是需要重點(diǎn)優(yōu)化的部分。
圖片延遲加載的原理就是先不設(shè)置img的src屬性,等合適的時(shí)機(jī)(比如滾動(dòng)、滑動(dòng)、出現(xiàn)在視窗內(nèi)等)再把圖片真實(shí)url放到img的src屬性上。更多內(nèi)容請移步上一篇博文: 圖片延遲加載方案

固定寬高值的圖片

固定寬高值的圖片延遲加載比較簡單,因?yàn)閷捀咧刀伎梢栽O(shè)置在css中,只需考慮src的替換問題,推薦使用lazysizes

// 引入js文件
<script src="lazysizes.min.js" async=""></script>

// 非響應(yīng)式 例子
<img src="" data-src="image.jpg" class="lazyload" />

// 響應(yīng)式 例子,自動(dòng)計(jì)算合適的圖片
<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標(biāo)簽的時(shí)候,如果src屬性為空,瀏覽器會(huì)認(rèn)為這個(gè)圖片是壞掉的圖,會(huì)顯示出圖片的邊框,影響市容。


第一塊是初始狀態(tài),第四塊是成功狀態(tài),第二塊第三塊是影響市容的狀態(tài)

lazysizes延遲加載過程中會(huì)改變圖片的class:默認(rèn)lazyload,加載中l(wèi)azyloading,加載結(jié)束:lazyloaded。結(jié)合這個(gè)特性我們有兩種解決上述問題辦法:
1、設(shè)置opacity:0,然后在顯示的時(shí)候設(shè)置opacity:1。

// 漸現(xiàn) lazyload
.lazyload,
.lazyloading{
    opacity: 0;
}
.lazyloaded{
    opacity: 1;
    transition: opacity 500ms; //加上transition就可以實(shí)現(xiàn)漸現(xiàn)的效果
}  

2、用一張默認(rèn)的圖占位,比如1x1的透明圖或者灰圖。

<img class="lazyload" 
    src="data:image/gif;base64,R0lGODlhAQABAAA
       AACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" 
    data-src="真實(shí)url" 
    alt="<%= article.title %>">

此外,為了讓效果更佳,尤其是文章詳情頁中的大圖,我們可以加上loading效果。

.article-detail-bd {
    .lazyload {
        opacity: 0;
    }
    .lazyloading {
        opacity: 1;
        background: #f7f7f7 url(/images/loading.gif) no-repeat center;
    }
}
固定寬高比的圖片

固定寬高比的圖片延遲加載相對來說復(fù)雜很多,比如文章詳情頁的圖片,由于設(shè)備的寬度值不確定,所以高度值也不確定,這時(shí)候工作的重心反倒放到了如何確定圖片的高度上。
為什么要確定圖片的高度呢?因?yàn)閱蝹€(gè)圖片的加載是從上往下,所以會(huì)導(dǎo)致頁面抖動(dòng),不僅用戶體驗(yàn)很差,而且對于性能消耗很大,因?yàn)槊看味秳?dòng)都會(huì)觸發(fā)reflow(重繪)事件,之前的博文 網(wǎng)站性能優(yōu)化 之 渲染性能 也分析過重繪對于性能的消耗問題。

固定寬高比的圖片抖動(dòng)問題,有下列兩種主流的方式可以解決:
1、第一種方案使用padding-top或者padding-bottom來實(shí)現(xiàn)固定寬高比。優(yōu)點(diǎn)是純CSS方案,缺點(diǎn)是HTML冗余,并且對輸出到第三方不友好。

<div style="padding-top:75%">
    <img data-src="" alt="" class="lazyload">
<div>

2、第二種方案在頁面初始化階段利用ratio設(shè)置實(shí)際寬高值,優(yōu)點(diǎn)是html干凈,對輸出到第三方友好,缺點(diǎn)是依賴js,理論上會(huì)至少抖動(dòng)一次。

<img data-src="" alt="" class="lazyload" data-ratio="0.75">

那么,這個(gè)padding-top: 75%;data-ratio="0.75"的數(shù)據(jù)從哪兒來呢?在你上傳圖片的時(shí)候,需要后臺(tái)給你返回原始寬高值,計(jì)算得到寬高比,然后保存到data-ratio上。

好奇心日報(bào)采用的第二種方案,主要在于第一種方案對第三方輸出不友好:需要對img設(shè)置額外的樣式,但第三方平臺(tái)通常不允許引入外部樣式。

確定第二種方案之后,我們定義了一個(gè)設(shè)置圖片高度的函數(shù):

// 重置圖片高度,僅限文章詳情頁
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';
        }
    }
}

我們將以上代碼的定義和調(diào)用都直接放到了HTML中,就為了一個(gè)目的,第一時(shí)間計(jì)算圖片的高度值,降低用戶感知到頁面抖動(dòng)的可能性,保證最佳效果。

// 原生代碼
<img alt="" 
    data-ratio="0.562500" 
    data-format="jpeg" 
    class="lazyload" 
    data-src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg" 
    src="">

// 解析之后的代碼
<img alt="" 
    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做進(jìn)一步的優(yōu)化。

注意事項(xiàng)

1、避免圖片過早加載,把臨界值調(diào)低一點(diǎn)。在實(shí)際項(xiàng)目中,并不需要過早就把圖片請求過來,尤其是Mobile項(xiàng)目,過早請求不僅浪費(fèi)流量,也會(huì)因?yàn)檎埱筇啵瑢?dǎo)致頁面加載速度變慢。
2、為了最好的防抖效果,設(shè)置圖片高度的JS代碼內(nèi)嵌到HTML中以便第一時(shí)間執(zhí)行。
3、根據(jù)圖片寬度設(shè)置高度時(shí),使用clientWidth而不是width。這是因?yàn)镾afari中,第一時(shí)間執(zhí)行的JS代碼獲取圖片的width失敗,所以使用clientWidth解決這個(gè)問題。

4、JS/CSS按需打包

按需打包是webpack獨(dú)特的優(yōu)勢,如果有需要通過此種方式來管理模塊之間的依賴關(guān)系,強(qiáng)烈推薦引入!webpack門檻較高,可以看看我之前的博客:
webpack 入門
webpack 模塊化機(jī)制

好奇心日報(bào)是典型的多頁應(yīng)用,為了緩存通用代碼,我們使用webpack按需打包的同時(shí),還利用webpack的CommonsChunkPlugin 插件抽離出公用的JS/CSS代碼,便于緩存,在請求數(shù)量和公用代碼的緩存之間做了一個(gè)很好的平衡。

5、延遲加載ga統(tǒng)計(jì)

async & defer屬性

html5中給script標(biāo)簽引入了async和defer屬性。
帶有async屬性的script標(biāo)簽,會(huì)在瀏覽器解析時(shí)立即下載腳本同時(shí)不阻塞后續(xù)的document渲染和script加載等事件,從而實(shí)現(xiàn)腳本的異步加載。
帶有defer屬性的script標(biāo)簽,和async擁有類似的功能。并且他們有可以附帶一個(gè)onload事件<script src="" defer onload="init()">
async和defer的區(qū)別在于:async屬性會(huì)在腳本下載完成后無序立即執(zhí)行,defer屬性會(huì)在腳本下載完成后按照document結(jié)構(gòu)順序執(zhí)行。

由于defer和async的兼容性問題,我們通常使用動(dòng)態(tài)創(chuàng)建script標(biāo)簽的方式來實(shí)現(xiàn)異步加載腳本,即document.write('<script src="" async></script>');,該方式也可以避免阻塞。

ga統(tǒng)計(jì)代碼

ga統(tǒng)計(jì)代碼采用就是動(dòng)態(tài)創(chuàng)建script標(biāo)簽方案。
該方法不阻塞頁面渲染,不阻塞后續(xù)請求,但會(huì)阻塞window.onload事件,頁面的表現(xiàn)方式是進(jìn)度條一直加載或loading菊花一直轉(zhuǎn)。
所以我們延遲執(zhí)行g(shù)a初始化代碼,將其放到window.onload函數(shù)中去執(zhí)行,可以防止ga腳本阻塞window.onload事件。從而讓用戶感受到更快的加載速度。

將ga加載綁定到onload事件上

如何減小請求大小?

1、JS/CSS/HTML壓縮

這也是常規(guī)手段,就不介紹太多,主要的方式有:
1、通過構(gòu)建工具實(shí)現(xiàn),比如webpack/gulp/fis/grunt等。
2、后臺(tái)預(yù)編譯。
3、利用第三方online平臺(tái),手動(dòng)上傳壓縮。
無論是第二種還是第三種方式,都有其局限性,第一種方法是目前的主流方式,憑借良好的插件生態(tài),可以實(shí)現(xiàn)豐富的構(gòu)建任務(wù)。
在好奇心日報(bào)的項(xiàng)目中,我們使用webpack & gulp作為構(gòu)建系統(tǒng)的基礎(chǔ)。

簡單介紹一下JS/CSS/HTML壓縮方式和一些注意事項(xiàng)

JS壓縮

JS壓縮:使用webpack的UglifyJsPlugin插件,同時(shí)做一些代碼檢測。

new webpack.optimize.UglifyJsPlugin({
    mangle: {
        except: ['$super', '$', 'exports', 'require']
    }
})
CSS壓縮

CSS壓縮:使用cssnano壓縮,同時(shí)使用postcss做一些自動(dòng)化操作,比如自動(dòng)加前綴、屬性fallback支持、語法檢測等。

    var postcss = [
        cssnano({
            autoprefixer: false,
            reduceIdents: false,
            zindex: false,
            discardUnused: false,
            mergeIdents: false
        }),
        autoprefixer({ browers: ['last 2 versions', 'ie >= 9', '> 5% in CN'] }),
        will_change,
        color_rgba_fallback,
        opacity,
        pseudoelements,
        sorting
    ];
HTML壓縮

HTML壓縮:使用htmlmin壓縮HTML,同時(shí)對不規(guī)范的HTML寫法糾正。

// 構(gòu)建視圖文件-build版本
gulp.task('build:views', ['clean:views'], function() {
    return streamqueue({ objectMode: true },
            gulp.src(config.commonSrc, { base: 'src' }),
            gulp.src(config.layoutsSrc, { base: 'src' }),
            gulp.src(config.pagesSrc, { base: 'src/pages' }),
            gulp.src(config.componentsSrc, { base: 'src' })
        )
        .pipe(plumber(handleErrors))
        .pipe(logger({ showChange: true }))
        .pipe(preprocess({ context: { PROJECT: project } }))
        .pipe(gulpif(function(file) {
            if (file.path.indexOf('.html') != -1) {
                return true;
            } else {
                return false;
            }
        }, htmlmin({
            removeComments: true,
            collapseWhitespace: true,
            minifyJS: true,
            minifyCSS: true,
            ignoreCustomFragments: [/<%[\s\S]*?%>/, 
                                    /<\?[\s\S]*?\?>/, 
                                    /<meta[\s\S]*?name="viewport"[\s\S]*?>/]
        })))
        .pipe(gulp.dest(config.dest));
});

某個(gè)第三方平臺(tái)要求<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">必須寫成小數(shù)點(diǎn)格式,而htmlmin默認(rèn)會(huì)將小數(shù)格式化為整數(shù),所以額外添加了排除項(xiàng):/<meta[\s\S]*?name="viewport"[\s\S]*?>/。到現(xiàn)在都沒懂這個(gè)第三方平臺(tái)咋想的!

條件編譯

由于好奇心日報(bào)項(xiàng)目較多,我們費(fèi)了很大的心思抽離出前端項(xiàng)目,實(shí)現(xiàn)了前后分離。但有些場景下,我們?yōu)榱藢⑾嚓P(guān)代碼維護(hù)在一個(gè)文件中,同時(shí)又針對不同項(xiàng)目執(zhí)行不同的邏輯,這時(shí)候,強(qiáng)烈推薦使用 gulp-preprocess插件 來實(shí)現(xiàn)條件編譯。

條件編譯

2、gzip壓縮

gzip壓縮也是比較常規(guī)的優(yōu)化手段。前端并不需要做什么實(shí)際的工作,后臺(tái)配置下服務(wù)器就行,效果非常明顯。如果你發(fā)現(xiàn)你的網(wǎng)站還沒有配置gzip,那么趕緊行動(dòng)起來吧。

gzip壓縮原理

如果瀏覽器支持gzip壓縮,在發(fā)送請求的時(shí)候,請求頭中會(huì)帶有Accept-Encoding:gzip。然后服務(wù)器會(huì)將原始的response進(jìn)行g(shù)zip壓縮,并將gzip壓縮后的response傳輸?shù)綖g覽器,緊接著瀏覽器進(jìn)行g(shù)zip解壓縮,并最終反饋到網(wǎng)頁上。

支持gzip壓縮的請求頭
gzip壓縮效果

那么gzip壓縮的效果有多明顯呢?保守估計(jì),在已經(jīng)完成JS/CSS/HTML壓縮的基礎(chǔ)上,還能降低60-80%左右的大小。


gzip壓縮效果

但需要注意,gzip壓縮會(huì)消耗服務(wù)器的性能,不能過度壓縮。
所以推薦只對JS/CSS/HTML等資源做gzip壓縮。圖片的話,托管到第三方的圖片建議開啟gzip壓縮,托管到自己應(yīng)用服務(wù)器的圖片不建議開啟gzip壓縮。

3、JS/CSS按需加載

和前面提到的按需打包不同。
JS/CSS按需打包是預(yù)編譯發(fā)生的事情,保證只打包當(dāng)前頁面相關(guān)的邏輯。
JS/CSS按需加載是運(yùn)行時(shí)發(fā)生的事情,保證只加載當(dāng)前頁面第一時(shí)間使用到的邏輯。

那么怎么實(shí)現(xiàn)按需加載呢?好奇心日報(bào)使用webpack提供的requirerequire.ensure方法來實(shí)現(xiàn)按需加載,值得一提的是,除了指定的按需加載文件列表,webpack還會(huì)自動(dòng)解析回調(diào)函數(shù)的依賴及指定列表的深層次依賴,并最終打包成一個(gè)文件。

webpack按需加載

上訴代碼的實(shí)現(xiàn)效果是:只有當(dāng)點(diǎn)擊登錄按鈕的時(shí)候,才會(huì)去加載登錄相關(guān)的JS/CSS資源。資源在加載成功后自動(dòng)執(zhí)行。

4、圖片壓縮,jpg優(yōu)化

托管到應(yīng)用服務(wù)器的圖片壓縮

可以手動(dòng)處理,也可以通過gulp子任務(wù)來處理。
手動(dòng)處理的話,推薦一個(gè)網(wǎng)站 tinypng ,雖然是有損壓縮,但壓縮效果極好。
gulp子任務(wù)處理的話,推薦使用gulp-imagemin插件,自動(dòng)化處理,效果也還不錯(cuò)。

// 圖片壓縮
gulp.task('images', function() {
    return gulp.src(config.src)
        .pipe(plumber(handleErrors))
        .pipe(newer(config.dest))
        .pipe(logger({ showChange: true }))
        .pipe(imagemin()) // 壓縮
        .pipe(gulp.dest(config.dest));
});
托管到第三方平臺(tái)的圖片壓縮

比如七牛云平臺(tái),他們會(huì)有一套專門的方案來對圖片壓縮,格式轉(zhuǎn)換,裁剪等。只需要在url后面加上對應(yīng)的參數(shù)即可,雖然偶爾會(huì)有一些小bug,但整體來說,托管方案比用自家應(yīng)用服務(wù)器方案更優(yōu)。


改變參數(shù),實(shí)現(xiàn)不同程度的壓縮
jpg優(yōu)化

除了對圖片進(jìn)行壓縮之外,對透明圖床沒有要求的場景,強(qiáng)烈建議將png轉(zhuǎn)換為jpg,效果很明顯!
如下圖,將png格式化為jpg格式,圖片相差差不多8倍!


png轉(zhuǎn)jpg,體積相差八倍

再次強(qiáng)調(diào),可以轉(zhuǎn)換成jpg的圖片,強(qiáng)烈建議轉(zhuǎn)換成jpg!

5、webp優(yōu)化 & srcset優(yōu)化

webp優(yōu)化

粗略看一眼,臥槽,兼容性這么差,也就安卓瀏覽器及chrome瀏覽器對它的支持還算給力。


webp兼容性

另一方面,webp優(yōu)化能在jpg的基礎(chǔ)上再降低近50%的大小。其優(yōu)化效果明顯。此外,如果瀏覽器支持webpanimation,還能對gif做壓縮!


普通圖片webp優(yōu)化

gif圖片優(yōu)化

兼容性差,但效果好!最終好奇心決定嘗試一下。
1、判斷瀏覽器對webp及webpanimation的兼容性。
2、如果瀏覽器支持webp及webpanimation,將其替換成webp格式的圖片。

鑒于瀏覽器對webp的支持比較局限,我們采用漸進(jìn)升級的方式來優(yōu)化:對于不支持webp的瀏覽器,不做處理;對于支持webp的瀏覽器,將圖片src替換成webp格式。
那么如何判斷webp兼容性呢?

// 檢測瀏覽器是否支持webp
// 之所以沒寫成回調(diào),是因?yàn)榧词筰sSupportWebp=false也無大礙,但卻可以讓代碼更容易維護(hù)
(function() {
    function webpTest(src, name) {
        var img = new Image(),
            isSupport = false,
            className, cls;

        img.onload = function() {
            isSupport = !!(img.height > 0 && img.width > 0);

            cls = isSupport ? (' ' + name) : (' no-' + name);
            className = document.querySelector('html').className
            className += cls;

            document.querySelector('html').className = className.trim();
        };
        img.onerror = function() {
            cls = (' no-' + name);
            className = document.querySelector('html').className
            className += cls;

            document.querySelector('html').className = className.trim();
        };

        img.src = src;
    }

    var webpSrc = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoB\
                AAEAAwA0JaQAA3AA/vuUAAA=',
        webpanimationSrc = 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAA\
                            SAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAA\
                            AAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA';

    webpTest(webpSrc, 'webp');
    webpTest(webpanimationSrc, 'webpanimation');
})();

借鑒modernizr,實(shí)現(xiàn)了檢測webp/webpanimation兼容性的函數(shù),從代碼中可以看出,檢測原理就是模擬下載對應(yīng)格式的圖片,在異步函數(shù)中可以得到兼容性結(jié)果。

接下來就是替換url為webp格式

// 獲取webp格式的src
function _getWebpSrc(src) {
    var dpr = Math.round(window.devicePixelRatio || 1),
        ratio = [1, 1, 1.5, 2, 2, 2],
        elHtml = document.querySelector('html'),
        isSupportWebp = (/(^|\s)webp(\s|$)/i).test(elHtml.className),
        isSupportWebpAnimation = (/(^|\s)webpanimation(\s|$)/i).test(elHtml.className),
        deviceWidth = elHtml.clientWidth,
        isQiniuSrc = /img\.qdaily\.com\//.test(src),
        format = _getFormat(src),
        isGifWebp, isNotGifWebp, regDetailImg;
    
    if (!src || !isQiniuSrc || !format || format == 'webp') {
        return src;
    }

    isNotGifWebp = (format != 'gif' && isSupportWebp);
    isGifWebp = (format == 'gif' && isSupportWebpAnimation);

    // 根據(jù)屏幕分辨率計(jì)算大小
    src = src.replace(/\/(thumbnail|crop)\/.*?(\d+)x(\d+)[^\/]*\//ig, function(match, p0, p1, p2) {
        if(dpr > 1){
            p1 = Math.round(p1 * ratio[dpr]);
            p2 = Math.round(p2 * ratio[dpr]);

            match = match.replace(/\d+x\d+/, p1 + 'x' + p2)
        }

        return match;
    });

    if(isNotGifWebp || isGifWebp) {
       // 替換webp格式,首頁/列表頁
        src = src.replace(/\/format\/([^\/]*)/ig, function(match, p1) {
            return '/format/webp';
        });
    }
}
注意事項(xiàng)

1、window的屏幕像素密度不一定是整數(shù),mac瀏覽器縮放之后,屏幕像素密度也不是整數(shù)。所以獲取dpr一定要取整:dpr = Math.round(window.devicePixelRatio || 1);
2、ratio = [1, 1, 1.5, 2, 2, 2]表示:1倍屏使用1倍圖,2倍屏使用1.5倍圖,3倍屏以上都用2倍圖。這兒的規(guī)則可以按實(shí)際情況來設(shè)置。
3、webp優(yōu)化更適合托管到第三方的圖片,簡單修改參數(shù)就可以獲取不同的圖片。

devicePixelRatio兼容性

srcset兼容性
srcset兼容性

如上所述,在對webp優(yōu)化的時(shí)候,我們順道模擬實(shí)現(xiàn)了srcset:根據(jù)屏幕像素密度來設(shè)置最適合的圖片寬高。
lazysizes原本提供了srcset選項(xiàng),也可以借用lazysizes的方案來實(shí)現(xiàn)srcset,有興趣的可以去看看源碼。

又到總結(jié)的時(shí)候了?

本博客圍繞好奇心日報(bào)的具體實(shí)踐,在優(yōu)化頁面加載速度方面的做了一系列思考。整體來說,涉及的知識(shí)面比較廣:包括webpack & gulp的構(gòu)建系統(tǒng)、圖片的webp優(yōu)化、服務(wù)器的gzip配置、瀏覽器的加載順序、圖片延遲加載方案等等。

文中提到的gulp子任務(wù),后續(xù)也會(huì)有一系列好奇心日報(bào)項(xiàng)目的相關(guān)實(shí)踐,會(huì)覆蓋gulp子任務(wù)的設(shè)計(jì)思路,構(gòu)建系統(tǒng)的架構(gòu),以及具體子任務(wù)的剖析和講解,敬請關(guān)注。

如果該博文對你有一些幫助,請點(diǎn)擊喜歡支持一下,也歡迎在評論區(qū)留下你的建議和討論。

原創(chuàng)文章,未經(jīng)允許,嚴(yán)禁轉(zhuǎn)載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。 webpack介紹和使用 一、webpack介紹 1、由來 ...
    it筱竹閱讀 11,197評論 0 21
  • GitChat技術(shù)雜談 前言 本文較長,為了節(jié)省你的閱讀時(shí)間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,704評論 7 110
  • HTML5面試題總結(jié)1.基礎(chǔ)問題 = 和 == 和 === 的區(qū)別?= : 用于賦值 == : 用于判斷 === ...
    LorenaLu閱讀 1,198評論 0 4
  • 本文提到的網(wǎng)站性能指網(wǎng)站的響應(yīng)速度,這也符合絕大部分人對于網(wǎng)站性能的理解:訪問快速的網(wǎng)站性能好,反之,訪問速度越慢...
    qhaobaba閱讀 548評論 0 0
  • 在現(xiàn)在的前端開發(fā)中,前后端分離、模塊化開發(fā)、版本控制、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,460評論 1 32