你不可能知道的骨架屏玩法🐶

〇 前言

這篇是做者在公司作了活動架構升級後,產出的主文的前導第二篇,考慮到本文相對獨立,所以抽離出單獨成文。姐妹兄弟篇,《你可能不知道的動態組件玩法🍉》。javascript

可能文「對不起」題,知道的大佬別進來了😂,不、不,求吐槽、拍磚,瑞思拜。css

感謝昊神賜題😂,不要怪ssh,怪我。html

image.png

本文可能存在有些紕漏,但願你們多拍磚、建議,謝謝😘。前端

〇 背景

做者曾所在我司廣告事業部,廣告承載方式是以刮刮卡、大轉盤等活動頁進行展現,而後用戶參與出廣告券彈層。vue

image.png

這篇文章主要背景是這樣的,有業務方反饋,提議咱們能不能作一些對頁面流失率有提高的優化。java

所以針對活動頁面的數據狀況,咱們去作了測試。從測試數據反映,有些頁面加載完成率(專業的能夠理解爲首屏加載率)偏低,但活動頁面上一級入口點擊率正常。node

這種狀況有點奇怪啊,但經驗告訴咱們,通常就是用戶在點擊上一級入口進來後,因爲等待白屏時間過長,用戶可能覺得白屏是掛了或者忍受不了等待白屏的焦慮,沒有耐心就流失了。webpack

image.png

怎麼去縮短白屏時間呢?git

那就是讓用戶能夠更快看到非「白色」,能夠能聯想到的,背景底色、背景圖、關鍵部位圖等。github

不知道你們有沒有使用過骨架屏,下面咱們就是用相似骨架屏的能力去解決這個問題。

〇 「骨架圖」實現

骨架屏基本就是詳細頁面元素未展示時,把DOM結構經過線條勾勒出來。而對於C端的營銷類活動頁面來講,並無比較標準的骨架,每一個活動有本身的輪廓,那怎麼辦呢?

咱們能夠經過背景色和圖片來達到相似的功效,所以咱們衍生出「骨架圖」的概念,其實也是一種骨架屏。

image.png

實現思路

以一個拆紅包的活動去看,咱們會發現用戶關注的內容,是圖中的「拆字紅包」和背景色。

image.png

咱們應該儘可能讓「拆」字紅包圖更快的展現。

下面是這個活動的渲染截圖,經過Chrome Dev Tools -> Network -> Disable Cache -> Fast 3G(4G、WIFI過快不易觀察) -> 右側 ⚙️ -> Capture ScreenShots。就能夠打開了。能夠看到一幀一幀的圖片。

咱們目的是想讓關鍵幀,下圖中的綠色框中的1.44s那幀能夠更早展示。

image.png

怎麼造成這麼一幀關鍵圖片呢🤔 ?能夠很天然的想到,一張靜態頁面。無非經過HTML、CSS、圖片渲染而成。

image.png

因此須要提供DOM結構、提供CSS、提供圖片,生成「靜態骨架圖」。

image.png

上面是一個普通的HTML開發。

🤔️ 但咱們不是在經過純HTML開發,怎麼才能拿到Vue頁面的DOM結構呢?

這裏可能同窗們有疑問,爲何須要單獨拿Vue的DOM結構。

咱們通常一個Vue項目通常都是掛載在某個根節點下,好比#app下。

<html>
  <head></head>
  <body>
    <div id="app"></div>
    <script src="/cdn/xxx/vue.js"></script>
  </body>
</html>
複製代碼

經過把Vue實例掛載在#app上。

// index.js
import Entry from './Entry.vue'

new Vue({
  render: h => h(Entry)
}).$mount('#app')
複製代碼

而後纔是真正這個組件對應的DOM結構(template)。

// Entry.vue
<template>
  <div class="entry">
    <img src="/cdn/xx/image.png"/>
    <button class="btn" type="button">請點擊</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
    }
  }
}
</script>
複製代碼

從上面能夠看出Vue的DOM結構實際是隱藏在.vue文件裏的,而咱們初始化渲染Vue前,實際只能拿到#app這個div。

所以咱們須要須要想辦法拿到Vue組件裏面的DOM結構。怎麼拿呢?

image.png

預渲染DOM

在開始前,先看看這個圖,表示了咱們大體的流程,有圖不迷路😂。

image.png

不知道你們有沒有據說過puppeteer,一個無頭瀏覽器,它能作什麼呢?通常咱們會使用它去運行線上目標頁面,去抓取一些數據。

這裏咱們利用它,去幫咱們截取Vue的DOM結構。本身咱們去使用puppetter去截取DOM會須要作幾個步驟,用無頭瀏覽器跑對應的頁面,而後等頁面把Vue組件渲染出,渲染完成把對應的#app下的DOM結構截取出來,而後保存下來。

因爲步驟並不難,但會涉及到挺多代碼,其實社區裏已經有大佬幫咱們把這些能力集成了,prerender-spa-plugin,就是它了。

image.png

使用prerender-spa-plugin能夠很容易的拿到DOM結構,它的原理就是先運行無頭瀏覽器,而後執行對應的App路由,截取展現出的頁面中的DOM結構。

prerender-spa-plugin的具體用法在這裏就不細講了,能夠參考官方文檔。

cheerio是一個方便咱們獲取內容的工具,看看官方解釋。

爲服務器特別定製的,快速、靈活、實施的jQuery核心實現。

要獲取Vue頁面的DOM結構,須要分兩步。

  1. 先預渲染構建,輸出預渲染獲取到的Vue頁面的關鍵DOM結構。
  2. 再正常構建把獲取到的DOM結構插入到頁面初始DOM上。

預渲染構建

image.png

下面是在預渲染構建裏,webpack的預渲染配置。

// 預渲染構建的配置

{
  plugins: [
    // 生成vue dom骨架
    new PrerenderSPAPlugin({
      // 原文件地址
      staticDir: path.join(__dirname, config.localDir),
      // 構建生成地址
      outputDir: path.join(__dirname, config.prerenderPath),
      routes: [ '/' ],
      // 獲取上下文
      postProcess (context) {
        // 獲取編譯後的html內容
        const html = context.html
				// 使用cheerio選擇器
        const $ = cheerio.load(html)
        // 預渲染刪除apple(通常C端頁面都有個蘋果免責協議,在預渲染的頁面是多餘的)
        $('#apple').remove()
        // 截取須要的html片斷
        const contentHtml = '<div class="app">' + $('.app').html() + '</div>'
        // 刪除掉一些首屏無關的image圖片
        context.html = contentHtml.replace(/<img[^>]*>/gi, '')
				// 匹配每一個標籤裏的style裏的內容
        context.html = context.html.replace(/style="[^"]*"/g, function (matched) {
          // 若是符合如下佈局屬性,則保留,這裏沒考慮margin、padding等
          const reg = /(width|height|left|top|bottom|right):[^rem]*rem;/g
          const result = matched.match(reg)
          if (result) return 'style="' + result.join('') + '"'
          return ''
        })
        
        // 輸出處理好的預渲染內容
        return context
      }
    })]
}
複製代碼

預渲染構建好了,拿到了Vue頁面的DOM結構,咱們開始正式構建。

正式構建

image.png

這裏咱們本身寫個Webpack插件,關於怎麼寫Webpack插件這裏不贅述了。功能主要是把預渲染生成的DOM,插入到正式DOM中。

// 正式構建,利用插件的能力

const path = require('path')
const fs = require('fs')

// 命名插件,也能夠直接使用class定義,不是重點
function VueDomPrerenderPlugin (options) {
  this._options = options
}

VueDomPrerenderPlugin.prototype.apply = function (compiler) {
  const self = this
  compiler.hooks.compilation.tap('VueDomPrerenderPlugin', (compilation) => {
    // 經過html-webpack-plugin的hook
    compilation.plugin(
      'html-webpack-plugin-after-html-processing',
      (data, cb) => {
        // 找到預渲染輸出的文件
        const prerenderFile = path.join(self._options.preoutDir, 'index.html')

        // 把預渲染生成的DOM,插入到正式DOM中。
        let htmlContent = data.html.replace('<div id="app"></div>', (matched) => {
          const prerenderHtml = fs.readFileSync(prerenderFile, 'utf8')
          return '<div id="app" data-server-render="true">' + prerenderHtml + '</div>'
        })
      }
    )
  })
}

module.exports = VueDomPrerenderPlugin
複製代碼

但如今的是靜態的,頁面上的圖片、背景色都是定死的,怎麼才能動態設置圖片、背景色等呢?

image.png

動態設置數據

image.png

HTML、CSS只能作靜態頁面,但JavaScript能夠啊,JS能夠拿到數據,根據數據進行設置,那咱們的頁面就不是硬編碼的圖片和顏色了。

首先經過JS拿到對應的圖片、顏色數據,再找到DOM結構上對應的圖片佔位等,經過JS進行圖片設置,這裏就有三步。

// H5頁面中的代碼

let color = window.CFG.color
let bgImage = window.CFG.bgImage
let bgColor = window.CFG.bgColor
// 相似的還有其餘數據...

let $text = document.querySelector('.text')
let $bg = document.querySelector('.bg')
// 相似的還有不少...

$text.style.color = color
$bg.style.backgroundImage = 'url(' + bgImage + ')'
$bg.style.backgroundColor = bgColor
// 相似的還有不少...
複製代碼

🤔️ 經過上述JS這種命令式的方式,在樣式設置上的可讀性差;並且咱們模版代碼太多了,對業務侵入性比較強,對開發很不友好。怎麼辦呢?

image.png

減小重複代碼

咱們在思考,能不能就讓開發同窗,書寫代碼像寫css同樣,編碼去預設一些預加載的圖片和背景色等等呢?

能夠理解把上面的抽象點,可讓命令式的代碼變成聲明式的代碼,比較利於理解。

大體的流程。

image.png

定義模版

image.png

咱們想到了,能不能利用模版的能力,提供一個.tcss文件類型,這是一個類CSS的文件。能夠看到咱們經過**{{ }}**來提供變量設置能力。

image.png

image.png

解析模版

image.png

那怎麼解析這樣一個模版呢,咱們經過node以及正則表達式的能力。須要提供一段代碼邏輯,先讀取.tcss文件,而後經過替換模版爲真實可運行代碼。

// node腳本中的代碼
// 解析preload.tcss,輸出preloadCss、preloadImages

// 某個活動下
const BASE_FOLDER = `./src/pages/activity`
// 上述文件.tcss所在地址
const cssPath = path.join(BASE_FOLDER, 'preload.tcss')
// 獲取文件的字符串
const data = fs.readFileSync(cssPath).toString()

// 匹配字符串的開始
let start = 0
// 記錄預加載的圖片
const preloadImages = []

let preloadCss = data.replace(/{{(\S+)}}/g, function (matched, pattern, offset, string) {
  // 當前匹配到的字段的偏移量
  let end = offset

  // 截取非空字符串
  const substring = string.substring(start, end)

  // 只有image類型的纔會存到預加載數組裏
  if (substring.indexOf('url') !== -1) {
    preloadImages.push(`get('${pattern}')`)
  }

  // 上一次的終點爲此次的起點
  start = end

  // 把匹配到的例如image、color進行包裝
  return `' + get('${pattern}') + '`
})

// 統一成\n
preloadCss = preloadCss.replace(/(\r|\n|\r\n)/g, function (matched) {
  return `'\n+'`
})
複製代碼

大體代碼如上,會輸出生成預加載的圖片列表 - preloadImages、預加載樣式Style的JS片斷 - preloadCss。

生成style的JS片斷,這裏你們可能會奇怪怎麼是生成這樣的一段JS代碼,是由於咱們經過node腳本,先在本地預先構建了能夠「生成CSS的JS代碼」,最終這段代碼是頁面渲染的時候運行。爲何不是純CSS,由於咱們須要動態拿屬性值(image、color等)。

preloadCss大體以下圖所示。

image.png

preloadImages大體以下圖所示。

image.png

image.png

生產物料的代碼(圖片、CSS)

這裏圖片,咱們選擇了用最簡便的new Image去實現。

image.png

拿到了preloadImages、preloadCss後,咱們再調用公共方法去加載圖片、生成style片斷。

// node腳本中的代碼

const TARGET_PATH = './node_modules/.cache/preload-image/'

const outputFilePath = path.join(TARGET_PATH, 'index.js')

if (!fs.existsSync(TARGET_PATH)) {
  fs.mkdirSync(TARGET_PATH, { recursive: true })
}

const preloadCode = ` ;(function(win) { var preloadImages = [${preloadImages.join(',')}]; preloadImage(preloadImages); var styles = '${preloadCss}'; addPreloadStyle(styles); })(window); `

fs.writeFileSync(outputFilePath, preloadCode)
複製代碼

上面的代碼拼接成一串字符串,最終會經過內嵌到HTML頁面中,在頁面渲染時運行。

這樣咱們就完成了預加載的物料:圖片、樣式的準備了。下面須要把準備應用上到頁面上。

image.png

內聯代碼到頁面

image.png

把設置預加載圖片、樣式的JS之內聯的方式潛入到HTML。

<html>
  <head>
	<script src="./node_modules/.cache/preload-image/index.js?__inline"></script>
  </head>
</html>
複製代碼

你們能夠看到?__inline是作什麼的呢,它的效果就是把外聯JS內聯到HTML中。

<html>
  <head>
    <script> function preloadImage (arr) { for (let i = 0; i < arr.length; i++) { const images = [] images[i] = new Image() images[i].src = arr[i] } } function addPreloadStyle (styles) { const css = document.createElement('style') css.type = 'text/css' if (css.styleSheet) { css.styleSheet.cssText = styles } else { css.appendChild(document.createTextNode(styles)) } document.getElementsByTagName('head')[0].appendChild(css) } </script>
		<script> !function(win){ var preloadImages = [ get("bgImage"), // 省略了... ]; preloadImage(preloadImages); var preloadCss = '.tac-app {' + 'background-image: url("'+ get("bgImage")+'");' + 'background-color: '+ get("bgColor")+';' + '}' addPreloadStyle(preloadCss) (window); </script>
  </head>
</html>
複製代碼

image.png

「福利:__inline功能的webpack插件」

關於上面?__inline功能是怎麼實現的,能夠看看這個webpack插件的寫法。

/* * script標籤中含有?__inline標識的Js會被內聯到HTML。 */
const fs = require('fs')

const getScriptAbsolutePath = (matched, reg) => {
  const result = matched.match(reg)
  const relativePath = result && result[1]

  return relativePath
}

class ScriptInlinePlugin {
  apply (compiler) {
    compiler.hooks.compilation.tap('ScriptInlinePlugin', (compilation) => {
      // 因爲vuecli3使用的webpack-html-plugin是3.2版本,因此暫時不能使用tap形式。
      compilation.plugin(
        'html-webpack-plugin-after-html-processing',
        (data) => {
          // 讀取目標模版
          let htmlContent = data.html

          // 匹配script的reg
          const jsReg = /<script[^<]*?src="?[^<]*?\?__inline"?.*?>.*?<\/script>/gmi

          // 匹配script片斷
          htmlContent = htmlContent.replace(jsReg, (matched) => {
            // 獲取script絕對地址
            const absolutePath = getScriptAbsolutePath(matched, /src="?(.*)\?__inline/)

            // 獲取script對應地址的內容
            const jsContent = fs.readFileSync(absolutePath, 'utf-8')

            return `<script type="text/javascript">${jsContent}</script>`
          })

          data.html = htmlContent
        }
      )
    })
  }
}

module.exports = ScriptInlinePlugin

複製代碼

基本的功能已經具有了,但使用了一段時間後,發現有些問題。

體驗上的考慮

咱們發現功能是實現了,但對於開發體驗不太友好。

分析步驟

如今開發同窗須要開發一個骨架屏,須要幾個步驟。

  1. vue中設定預加載圖片的placeholder,這樣才能關聯上預加載好的圖片。
<template>
  <div :class="$style.app">
    <div :class="$style.button">
    </div>
    <div :class="$style.cat">
    </div>
  </div>
</template>

<style lang="less" module>
// 預渲染使用的全局class佔位符
:global {
  .app {}
  .button {}
  .cat {}
}
</style>
複製代碼
  1. 需知道獲取後端接口返回的哪些變量,而後設置到對應的.tcss文件裏
// backgroundColor
// backgroundImage

// buttonImage

// catImage
複製代碼
  1. tcss設定書寫對應的預加載樣式
.app {
  background-image: url("{{backgroundImage}}");
}

.button {
  background-image: url("{{buttonImage}}");
}

.cat {
  background-image: url("{{catImage}}");
}
複製代碼

image.png

這對開發同窗的心智成本仍是比較大的,他須要關心的太多了。

前置信息

下圖是一個活動的主要流程。

image.png

拋開細節,關注關鍵活動流程。

  1. 用戶進入活動頁面,服務器開始渲染;
  2. 活動開發提供活動代碼,基建開發提供公共代碼;
  3. 活動運營經過運營管理平臺配置活動;
  4. 服務器開始模版拼接,從數據庫獲取代碼及配置進行組合;
  5. 瀏覽器展示活動頁面。

由於咱們一個活動是固定主圖和樣式的,並無千人千面。

因此咱們能夠在運營管理平臺配置的時候就確認哪些圖片能夠被預先加載。

配置代替編碼

能夠在運營管理平臺配置,選擇須要預加載的圖片,好比下圖的背景圖。

image.png

把全部選擇的Image做爲一個列表,傳入後端SSR,因爲咱們Java後端做爲模版渲染,使用的Velocity模版。

// 經過Velocity循環渲染。
#foreach( $key in $preloadImageList )
    <link rel="preload" as="image" href="$preloadImageList.get($key)">
#end
複製代碼

這樣咱們就讓一線同窗從編碼預加載的負擔中,解放了出來。

工程化的事情完工了,下面咱們須要看看怎麼優化咱們的性能了,更主要的是在於圖片。

〇 更快的圖片加載

注意⚠️,下文中關於network的查看,先進行以下操做,Chrome Dev Tools -> Network -> Disable Cache -> Fast 3G。

資源加載順序

上文咱們經過new Image的方式來進行圖片的加載,但會遇到問題。

new Image加載

咱們經過個例子來了解下這個問題。這個頁面須要加載3張圖片、2個CSS文件、6個JS文件。

<!DOCTYPE html>
<html>
<head>
    <script> function preloadImage(arr) { for (let i = 0; i < arr.length; i++) { const images = [] images[i] = new Image() images[i].src = arr[i] } } preloadImage( [ 'http://yun.tuisnake.com/tuia/dist/image/1.jpg', 'http://yun.tuisnake.com/tuia/dist/image/2.png', 'http://yun.tuisnake.com/tuia/dist/image/3.png' ]) </script>
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

咱們發現圖片是在前四個JS文件下載完,纔開始下載。但咱們但願圖片能夠更早的下載。

image.png

對上圖不瞭解的同窗,能夠閱讀下,Timing breakdown phases explained

這是爲何呢?首先,在Http1.1協議中,同域名下同時只能打開6個TCP連接,所以須要進行資源排隊,能夠看到上面的紅色框是在排隊的。

資源是有優先級的

但這好像也不能足夠說明吧🧐,咱們繼續尋找緣由。

image.png

咱們發現有個字段叫Priority,圖片資源是Low,看樣子圖片好像是排隊裏優先級最低的。

image.png

但這個優先級是幹啥用的啊?

想想🤔️咱們的瀏覽器是怎麼知道哪些資源先下載的呢?這裏不賣關子了。

  1. 首先要清楚對瀏覽器資源的進行分門別類
  2. 而後對資源的優先級進行計算
  3. 最後根據資源的優先級進行順序地下載

這裏擴展閱讀能夠看看:瀏覽器頁面資源加載過程與優化

從而咱們發現瀏覽器是有資源加載優先級的,好比 CSS、HTML、JS等都是核心資源,因此優先級最高;而圖片、視音頻就不是核心資源,優先級就比較低。一般當後者遇到前者時,就須要「讓路」,進入待排隊狀態。

圖片的優先級

image.png

再回到本文,爲啥咱們的圖片優先級是Low呢,能不能提高呢?那咱們先來了解個知識點。

Avoid chaining critical requests裏提到一份瀏覽器優先級細分報告(由Pat Meenan提供),顯示了從Chrome 46及更高版本開始,Blink 內核的 Chrome 如何優先處理不一樣的資源。

下圖就是上述文章裏,有關Chrome的加載優先級,能夠觀摩一下。

PS:這張圖是2015年的,可能如今瀏覽器的行爲會有出入。但願有知道更新一版的同窗請留言呀,謝謝。

image.png

咱們能夠看到Image有兩種類型的優先級,一種是在視圖內的 - Image(in viewport),另外一種是視圖外的 - Image。分別對應了High、Low優先級。

咱們也在另外篇文章裏發現了論證此點的線索,在web.dev的Fast load times模塊中的Prioritize resources文章介紹。

for example, an image that is part of the initial render is prioritized higher than an image that starts offscreen.

背景圖形式加載

理論都這麼說,咱們驗證下。咱們給3個圖片都增長了樣式、DOM、背景圖設置,使得它們存在在視圖內。

<!DOCTYPE html>
<html>
<head>
    <style> .image1, .image2, .image3 { height: 300px; width: 400px; } .image1 { background-image: url('http://yun.tuisnake.com/tuia/dist/image/1.jpg'); } .image2 { background-image: url('http://yun.tuisnake.com/tuia/dist/image/2.png'); } .image3 { background-image: url('http://yun.tuisnake.com/tuia/dist/image/3.png'); } </style>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <div class="image1"></div>
    <div class="image2"></div>
    <div class="image3"></div>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

結果以下圖。

image.png

從上面圖片看,確實是圖片的優先級被提高到了High,但仍是在js文件後面加載了,這是爲何?

以css背景圖存在的圖片background-image,會等到結構加載完成(網頁的內容所有顯示之後)纔開始加載;而html中的標籤img是網頁結構(內容)的一部分,會在加載結構的過程當中加載。

那咱們試試直接使用img標籤呢?

image.png

標籤形式加載

<!DOCTYPE html>
<html>
<head>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 修改點 -->
 	  <img src="http://yun.tuisnake.com/tuia/dist/image/1.jpg"/>
    <img src="http://yun.tuisnake.com/tuia/dist/image/2.png"/>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
</body>
</html>
複製代碼

咱們看到圖片確實是第一時間加載了。

image.png

真的是這樣嗎,圖片若是是img標籤形式就提早加載嗎?

咱們再換一個DEMO試試,2個CSS文件、4個JS文件。

<!DOCTYPE html>
<html>
<head>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 修改點 -->
 	  <img src="http://yun.tuisnake.com/tuia/dist/image/1.jpg"/>
    <img src="http://yun.tuisnake.com/tuia/dist/image/2.png"/>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
</body>
</html>
複製代碼

咱們看看結果。

image.png

咱們發現,兩個圖片資源滯後了。這是爲何啊???

image.png

再回憶我以前這節前埋的伏筆,2個CSS+4個JS,已經佔滿了一個域名6個請求的限制,圖片就滯後了。

那爲何圖片搶不過CSS、JS呢?再回頭看看這張圖。

image.png

Image在初始化的時候默認優先級都爲Low,只有等瀏覽器渲染到圖片的時候,計算是否在視圖內把圖片提到優先級爲High。有看出什麼眉目嗎,由於CSS、Script的優先級都會比Image來的高。

經過image加載圖片也不是很可靠,還有其餘辦法嗎?

image.png

優先圖片加載

咱們來看看一個API,preload。

方法一:preload加載圖片

還能夠閱讀下這篇文章Preload, Prefetch And Priorities in Chrome瞭解,這有翻譯版,Preload,Prefetch 和它們在 Chrome 之中的優先級性能優化

圖片優先級提高了,但並不必定第一時間加載。怎麼才能強制提高圖片的加載順序呢?

  1. html、css、font這三種類型的資源優先級最高;
  2. 而後是preload資源(經過<link rel=「preload">標籤預加載)、script、xhr請求。
<!DOCTYPE html>
<html>
<head>
    <!-- 修改點 -->
    <link rel="preload" as="image" href="http://yun.tuisnake.com/tuia/dist/image/1.jpg">
    <link rel="preload" as="image" href="http://yun.tuisnake.com/tuia/dist/image/2.png">

    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script src="http://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

經過preload咱們能夠改變瀏覽器資源加載順序。

image.png

能夠從上圖看到,咱們的圖片確實第一時間加載了。

image.png

還有其餘辦法嗎?

方法二:單獨圖片域名

咱們知道http1.1同域名下,限制6個連接,那咱們能夠試試多個域名?給圖片另外一個獨特域名。

<!DOCTYPE html>
<html>
<head>
    <script> function preloadImage(arr) { for (let i = 0; i < arr.length; i++) { const images = [] images[i] = new Image() images[i].src = arr[i] } } </script>
    <!-- 測試圖片3張 -->
    <script> preloadImage( [ 'https://yun.tuipink.com/tuia/dist/image/1.jpg', 'https://yun.tuipink.com/tuia/dist/image/2.png', 'https://yun.tuipink.com/tuia/dist/image/3.png' ]) </script>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="https://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="https://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script src="https://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

能夠很明顯看到圖片也在第一時間進行了加載。

image.png

看起來也挺好的,是否是,還有沒有其餘方式呢?

方法三:下降其餘資源優先級

經過優先級表,咱們知道若是把JS延後加載,相對於就是提早了圖片加載。

咱們先能夠考慮下async、defer標記。

image.png

async vs defer attributes - Growing with the Web

但發現async、defer並不會改變js文件請求的順序,依舊是排在image前面。

「async」

<!DOCTYPE html>
<html>
<head>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <image src="http://yun.tuisnake.com/tuia/dist/image/1.jpg"></image>
    <image src="http://yun.tuisnake.com/tuia/dist/image/2.png"></image>
    <image src="http://yun.tuisnake.com/tuia/dist/image/3.png"></image>
    <!-- 利用vue做爲測試js,有6個 -->
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script async src="http://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

圖片依舊是最後加載。

image.png

「defer」

<!DOCTYPE html>
<html>
<head>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="http://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <image src="http://yun.tuisnake.com/tuia/dist/image/1.jpg"></image>
    <image src="http://yun.tuisnake.com/tuia/dist/image/2.png"></image>
    <image src="http://yun.tuisnake.com/tuia/dist/image/3.png"></image>
    <!-- 利用vue做爲測試js,有6個 -->
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue1.js"></script>
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue2.js"></script>
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue3.js"></script>
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue4.js"></script>
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue5.js"></script>
    <script defer src="http://yun.tuisnake.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

也是不行的,圖片依舊最後加載。

image.png

上面兩種方式不行,還有其餘方式嗎?

「JS Loader」

既然原生的不行,咱們來JS代碼控制Script、CSS插入的時機。

<!DOCTYPE html>
<html>
<head>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="https://yun.tuisnake.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="https://yun.tuisnake.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 修改點 -->
    <image src="https://yun.tuisnake.com/tuia/dist/image/1.jpg"></image>
    <image src="https://yun.tuisnake.com/tuia/dist/image/2.png"></image>
    <image src="https://yun.tuisnake.com/tuia/dist/image/3.png"></image>

    <script src="https://yun.tuisnake.com/tuia/dist/js/loader.js"></script>
    <script> Loader.async([ 'https://yun.tuisnake.com/tuia/dist/js/vue1.js', 'https://yun.tuisnake.com/tuia/dist/js/vue2.js', 'https://yun.tuisnake.com/tuia/dist/js/vue3.js', 'https://yun.tuisnake.com/tuia/dist/js/vue4.js', 'https://yun.tuisnake.com/tuia/dist/js/vue5.js', 'https://yun.tuisnake.com/tuia/dist/js/vue6.js' ]) </script>
</body>
</html>
複製代碼

能夠看到明顯的圖片優先加載了。

image.png

以前說了那麼多都是針對Http1.1的,咱們如今看看Http2.0狀況下。

方法四:Http2.0

咱們須要開啓Http2.0,yun.tuiapple.com這個域名是開啓了Http2.0的。

<!DOCTYPE html>
<html>
<head>
    <script> function preloadImage(arr) { for (let i = 0; i < arr.length; i++) { const images = [] images[i] = new Image() images[i].src = arr[i] } } </script>
    <!-- 測試圖片3張 -->
    <script> preloadImage( [ 'https://yun.tuiapple.com/tuia/dist/image/1.jpg', 'https://yun.tuiapple.com/tuia/dist/image/2.png', 'https://yun.tuiapple.com/tuia/dist/image/3.png' ]) </script>
    <!-- 利用bootstrap做爲測試css,有2個 -->
    <link rel="stylesheet" href="https://yun.tuiapple.com/tuia/dist/css/bootstrap1.css">
    <link rel="stylesheet" href="https://yun.tuiapple.com/tuia/dist/css/bootstrap2.css">
</head>

<body>
    <!-- 利用vue做爲測試js,有6個 -->
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue1.js"></script>
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue2.js"></script>
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue3.js"></script>
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue4.js"></script>
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue5.js"></script>
    <script src="https://yun.tuiapple.com/tuia/dist/js/vue6.js"></script>
</body>
</html>
複製代碼

咱們發現圖片幾乎是和CSS、JS同時發起下載的。

image.png

最終方案

由於咱們的業務環境是要對接不少媒體,但有一些媒體並不支持Https,所以咱們同時須要考慮Http2.0和Http1.0的環境。

在Https支持的狀況下,咱們使用Http2.0方案;在不支持Https的狀況下,咱們使用link preload結合圖像單域名。方式三約束了前端加載的方式,侵入性較強,暫不作考慮。

關於preload,瀏覽器通過幾年的發展,兼容性沒什麼大問題,能夠參見caniuse

更快的背景圖

相對來講背景圖仍是比較大的,怎麼才能讓它更快的展現呢?

漸進式

JPEG類型有一種漸進JPEG,能夠在網絡差的狀況下避免徹底白屏。

6a0120a85dcdae970b0128776fcab6970c.gif

漸進式jpeg(progressive jpeg)圖片及其相關

壓縮

咱們還能夠經過壓縮圖片,對JPEG、PNG格式進行壓縮。咱們專門作了一個圖片壓縮服務,這在後續章節會進行介紹。

image.png

一樣一張圖片,通過webp壓縮事後,會更小。

緩存

咱們還能夠利用緩存,強制圖片進行強緩存,加快第二次。

image.png

能夠看到第二次,咱們就從瀏覽器的Cache去取到了圖片,都不須要用戶請求。

內嵌

經過內聯base64「小圖」,能夠看看這個小demo,具體的構建再也不這裏贅述,提供一個思路。

〇 簡要小結

相似骨架屏的一種實現。

  1. 經過無頭瀏覽器在構建前提早跑一次頁面,獲取當前DOM結構。
  2. 經過提供一個類css的模版(開發者編碼時,在模版中設置好圖片、顏色等),經過編譯生成一段js(具有加載圖片、生成css片斷能力),插入html頭部。
  3. 結合運行時動態生成的css、提早獲取的頁面dom結構、加載的圖片,一個大體的「骨架圖」就呈現了。
  4. 最後對於圖片加載作了一些討論。

仍是迴應下開頭,本文可能存在有些紕漏,但願你們多拍磚、建議,謝謝😘。

image.png

結語

回顧做者往期高贊文章,可能有意想不到的收穫!

😘點贊+評論+轉發😘,原創一篇不易,求鼓勵寫更多的文章

擴展閱讀

資源加載順序

瀏覽器頁面資源加載過程與優化

有興趣能夠從源碼角度分析

從Chrome源碼看瀏覽器如何加載資源 - 知乎

你們有興趣看JS的優先級,能夠參考

Chrome 中 JavaScript 加載優先級 | FENews

圖片壓縮

Use Imagemin to compress images

相關文章
相關標籤/搜索