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

发表于 3年以前  | 总阅读数:298 次

〇 前言

这篇是作者在公司做了活动架构升级后,产出的主文的前导第二篇,考虑到本文相对独立,因此抽离出单独成文。姐妹兄弟篇,《你可能不知道的动态组件玩法》。

本文可能存在有些纰漏,希望大家多拍砖、建议,谢谢。

〇 背景

作者曾所在我司广告事业部,广告承载方式是以刮刮卡、大转盘等活动页进行展示,然后用户参与出广告券弹层。

这篇文章主要背景是这样的,有业务方反馈,提议我们能不能做一些对页面流失率有提升的优化。

因此针对活动页面的数据情况,我们去做了测试。从测试数据反映,有些页面加载完成率(专业的可以理解为首屏加载率)偏低,但活动页面上一级入口点击率正常。

这种情况有点奇怪啊,但经验告诉我们,一般就是用户在点击上一级入口进来后,由于等待白屏时间过长,用户可能以为白屏是挂了或者忍受不了等待白屏的焦虑,没有耐心就流失了。

怎么去缩短白屏时间呢?

那就是让用户可以更快看到非“白色”,可以能联想到的,背景底色、背景图、关键部位图等。

不知道大家有没有使用过骨架屏,下面我们就是用类似骨架屏的能力去解决这个问题。

〇 “骨架图”实现

骨架屏基本就是详细页面元素未展现时,把DOM结构通过线条勾勒出来。而对于C端的营销类活动页面来说,并没有比较标准的骨架,每个活动有自己的轮廓,那怎么办呢?

我们可以通过背景色和图片来达到类似的功效,因此我们衍生出“骨架图”的概念,其实也是一种骨架屏。

实现思路

以一个拆红包的活动去看,我们会发现用户关注的内容,是图中的“拆字红包”和背景色。

我们应该尽量让“拆”字红包图更快的展示。

下面是这个活动的渲染截图,通过Chrome Dev Tools -> Network -> Disable Cache -> Fast 3G(4G、WIFI过快不易观察) -> 右侧 ⚙️ -> Capture ScreenShots。就可以打开了。可以看到一帧一帧的图片。

我们目的是想让关键帧,下图中的绿色框中的1.44s那帧可以更早展现。

怎么形成这么一帧关键图片呢 ?可以很自然的想到,一张静态页面。无非通过HTML、CSS、图片渲染而成。

所以需要提供DOM结构、提供CSS、提供图片,生成“静态骨架图”。

上面是一个普通的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结构。怎么拿呢?

预渲染DOM

在开始前,先看看这个图,表示了我们大致的流程,有图不迷路。

不知道大家有没有听说过puppeteer,一个无头浏览器,它能做什么呢?一般我们会使用它去运行线上目标页面,去抓取一些数据。

这里我们利用它,去帮我们截取Vue的DOM结构。自己我们去使用puppetter去截取DOM会需要做几个步骤,用无头浏览器跑对应的页面,然后等页面把Vue组件渲染出,渲染完成把对应的#app下的DOM结构截取出来,然后保存下来。

由于步骤并不难,但会涉及到挺多代码,其实社区里已经有大佬帮我们把这些能力集成了,prerender-spa-plugin,就是它了。

使用prerender-spa-plugin可以很容易的拿到DOM结构,它的原理就是先运行无头浏览器,然后执行对应的App路由,截取展示出的页面中的DOM结构。

prerender-spa-plugin的具体用法在这里就不细讲了,可以参考官方文档。

cheerio是一个方便我们获取内容的工具,看看官方解释。

为服务器特别定制的,快速、灵活、实施的jQuery核心实现。

要获取Vue页面的DOM结构,需要分两步。

  1. 先预渲染构建,输出预渲染获取到的Vue页面的关键DOM结构。
  2. 再正常构建把获取到的DOM结构插入到页面初始DOM上。

预渲染构建

下面是在预渲染构建里,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结构,我们开始正式构建。

正式构建

这里我们自己写个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

但现在的是静态的,页面上的图片、背景色都是定死的,怎么才能动态设置图片、背景色等呢?

动态设置数据

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这种命令式的方式,在样式设置上的可读性差;而且我们模版代码太多了,对业务侵入性比较强,对开发很不友好。怎么办呢?

减少重复代码

我们在思考,能不能就让开发同学,书写代码像写css一样,编码去预设一些预加载的图片和背景色等等呢?

可以理解把上面的抽象点,可以让命令式的代码变成声明式的代码,比较利于理解。

大致的流程。

定义模版

我们想到了,能不能利用模版的能力,提供一个.tcss文件类型,这是一个类CSS的文件。可以看到我们通过**{{ }}**来提供变量设置能力。

解析模版

那怎么解析这样一个模版呢,我们通过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大致如下图所示。

preloadImages大致如下图所示。

生产物料的代码(图片、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页面中,在页面渲染时运行。

这样我们就完成了预加载的物料:图片、样式的准备了。下面需要把准备应用上到页面上。

内联代码到页面

把设置预加载图片、样式的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>

「福利:__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>

2 . 需知道获取后端接口返回的哪些变量,然后设置到对应的.tcss文件里

// backgroundColor
// backgroundImage

// buttonImage

// catImage

3 . tcss设定书写对应的预加载样式

.app {
  background-image: url("{{backgroundImage}}");
}

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

.cat {
  background-image: url("{{catImage}}");
}

这对开发同学的心智成本还是比较大的,他需要关心的太多了。

前置信息

下图是一个活动的主要流程。

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文件下载完,才开始下载。但我们希望图片可以更早的下载。

对上图不了解的同学,可以阅读下,Timing breakdown phases explained

这是为什么呢?首先,在Http1.1协议中,同域名下同时只能打开6个TCP链接,因此需要进行资源排队,可以看到上面的红色框是在排队的。

资源是有优先级的

但这好像也不能足够说明吧,我们继续寻找原因。

我们发现有个字段叫Priority,图片资源是Low,看样子图片好像是排队里优先级最低的。

但这个优先级是干啥用的啊?

想一想️我们的浏览器是怎么知道哪些资源先下载的呢?这里不卖关子了。

  1. 首先要清楚对浏览器资源的进行分门别类
  2. 然后对资源的优先级进行计算
  3. 最后根据资源的优先级进行顺序地下载

这里扩展阅读可以看看:浏览器页面资源加载过程与优化。

从而我们发现浏览器是有资源加载优先级的,比如 CSS、HTML、JS等都是核心资源,所以优先级最高;而图片、视音频就不是核心资源,优先级就比较低。通常当后者遇到前者时,就需要“让路”,进入待排队状态。

图片的优先级

再回到本文,为啥我们的图片优先级是Low呢,能不能提升呢?那我们先来了解个知识点。

Avoid chaining critical requests里提到一份浏览器优先级细分报告(由Pat Meenan提供),显示了从Chrome 46及更高版本开始,Blink 内核的 Chrome 如何优先处理不同的资源。

下图就是上述文章里,有关Chrome的加载优先级,可以观摩一下。

PS:这张图是2015年的,可能现在浏览器的行为会有出入。希望有知道更新一版的同学请留言呀,谢谢。

我们可以看到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>

结果如下图。

从上面图片看,确实是图片的优先级被提升到了High,但还是在js文件后面加载了,这是为什么?

以css背景图存在的图片background-image,会等到结构加载完成(网页的内容全部显示以后)才开始加载;而html中的标签img是网页结构(内容)的一部分,会在加载结构的过程中加载。

那我们试试直接使用img标签呢?

标签形式加载

<!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>

我们看到图片确实是第一时间加载了。

真的是这样吗,图片如果是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>

我们看看结果。

我们发现,两个图片资源滞后了。这是为什么啊???

再回忆我之前这节前埋的伏笔,2个CSS+4个JS,已经占满了一个域名6个请求的限制,图片就滞后了。

那为什么图片抢不过CSS、JS呢?再回头看看这张图。

Image在初始化的时候默认优先级都为Low,只有等浏览器渲染到图片的时候,计算是否在视图内把图片提到优先级为High。有看出什么眉目吗,因为CSS、Script的优先级都会比Image来的高。

通过image加载图片也不是很可靠,还有其他办法吗?

优先图片加载

我们来看看一个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我们可以改变浏览器资源加载顺序。

可以从上图看到,我们的图片确实第一时间加载了。

还有其他办法吗?

方法二:单独图片域名

我们知道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>

可以很明显看到图片也在第一时间进行了加载。

看起来也挺好的,是不是,还有没有其他方式呢?

方法三:降低其他资源优先级

通过优先级表,我们知道如果把JS延后加载,相对于就是提前了图片加载。

我们先可以考虑下async、defer标记。

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>

图片依旧是最后加载。

「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>

也是不行的,图片依旧最后加载。

上面两种方式不行,还有其他方式吗?

「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>

可以看到明显的图片优先加载了。

之前说了那么多都是针对Http1.1的,我们现在看看Http2.0情况下。

方法四:Http2.0

我们需要开启Http2.0,https://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同时发起下载的。

最终方案

因为我们的业务环境是要对接很多媒体,但有一些媒体并不支持Https,因此我们同时需要考虑Http2.0和Http1.0的环境。

在Https支持的情况下,我们使用Http2.0方案;在不支持Https的情况下,我们使用link preload结合图像单域名。方式三约束了前端加载的方式,侵入性较强,暂不做考虑。

关于preload,浏览器经过几年的发展,兼容性没什么大问题,可以参见caniuse。

更快的背景图

相对来说背景图还是比较大的,怎么才能让它更快的展示呢?

渐进式

JPEG类型有一种渐进JPEG,可以在网络差的情况下避免完全白屏。

渐进式jpeg(progressive jpeg)图片及其相关

压缩

我们还可以通过压缩图片,对JPEG、PNG格式进行压缩。我们专门做了一个图片压缩服务,这在后续章节会进行介绍。

同样一张图片,经过webp压缩过后,会更小。

缓存

我们还可以利用缓存,强制图片进行强缓存,加快第二次。

可以看到第二次,我们就从浏览器的Cache去取到了图片,都不需要用户请求。

内嵌

通过内联base64“小图”,可以看看这个小demo,具体的构建不再这里赘述,提供一个思路。

〇 简要小结

类似骨架屏的一种实现。

  1. 通过无头浏览器在构建前提前跑一次页面,获取当前DOM结构。
  2. 通过提供一个类css的模版(开发者编码时,在模版中设置好图片、颜色等),通过编译生成一段js(具备加载图片、生成css片段能力),插入html头部。
  3. 结合运行时动态生成的css、提前获取的页面dom结构、加载的图片,一个大致的“骨架图”就呈现了。
  4. 最后对于图片加载做了一些讨论。

还是回应下开头,本文可能存在有些纰漏,希望大家多拍砖、建议,谢谢。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/LopLJxzUEbzeC8tHL4J-ew

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录