目录

白屏时间

1.定义

白屏时间指的是从用户进入网站,一直到页面有内容展示出来的时间节点,在这期间用户什么东西都看不到,这个过程包括dns查询、建立tcp连接、发送首个http请求、返回html文档、html文档head解析完毕。白屏时间的长短,影响着用户的体验,白屏时间过长,用户失去了耐心等待,也许就关闭了网页

一份是 Akamai 的研究报告,当时总共采访了大约 1048 名网上购物者,得出了这样的结论:

  • 大约有 47% 的用户期望他们的页面在两秒之内加载完成。
  • 如果页面加载时间超过 3s,大约有 40% 的用户选择离开或关闭页面。

白屏时间的计算就如上诉定义说的那样,只需要记录用户进入页面的开始时间,记录head加载完成的时间,记录两者的差值,这就是所说的白屏时间。计算的时候可以分别获取两个时间,计算他们的差,也可以使用performance.mark,在开始地点标记或者说打点,在结束地点获取这个entry,拿到它的duration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>白屏时间</title>
	<script>
		window.startTime = Date.now();
    // performance.mark("timeStr");
	</script>
	<link rel="stylesheet" href="">
	<script src=""></script>
	<script>
		window.endTime = Date.now()
    // const timeStrEntries = performance.getEntriesByName("timeStrEntries");
    // console.log(timeStrEntries[0].duration)
	</script>
</head>
<body>

</body>
</html>

另外还有一种方式,使用window.performance,在timing对象中,包含了各种时间相关的属性,其中domLoading属性代表开始解析DOM元素的时间,fetchStart属性代表发送请求前的时间,两者之差也可以计算白屏时间

对于具体的时间优化,可以针对整个页面渲染的过程来处理。这里说一下:预渲染服务端渲染使用骨架屏,这也可以说是用户的体验优化

2.预渲染

主要是用prerender-spa-plugin来实现这个功能。

prerender-spa-plugin 利用了 Puppeteer 的爬取页面的功能。 Puppeteer 是 Google Chrome 团队官方的无界面(Headless)Chrome 工具,它是一个 Node 库,提供了一个高级的 API 来控制 DevTools 协议上的无头版 Chrome 。prerender-spa-plugin 原理是在 Webpack 构建阶段的最后,在本地启动一个 Puppeteer 的服务,访问配置了预渲染的路由,然后将 Puppeteer 中渲染的页面输出到 HTML 文件中,并建立路由对应的目录。

首先请求到对应的路由或者页面之后,Puppeteer会获取渲染的页面的静态部分内容并替换打包出来的html文件,达到预渲染的目的,页面加载完成之前呈现给用户部分内容,给用户一个相对好的体验。但是缺点是预渲染出的内容往往与最终渲染的页面内容不同,因为页面会有相应的交互或者数据的变化,所以最好对于一些静态页面才用这种方式处理。

首先在对应的webpack配置文件中添加这个插件的配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const PrerenderSPAPlugin = require('prerender-spa-plugin');
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer;
const path = require('path');

module.exports = {
  plugins: [new PrerenderSPAPlugin({
    staticDir: path.join(__dirname, 'dist'),
    routes: [ '/' ], // 需要预渲染的路由,
    renderer: new Renderer({
        headless: true, // 开启无头浏览器
        renderAfterDocumentEvent: 'render-event', 
    }),
})

然后在页面触发render-event事件,告诉Puppeteer去爬取这个页面,这里以vue为例

1
2
3
4
5
export default {
  mounted() {
    document.dispatchEvent(new Event('render-event'));
  }
};

然后访问对应的route,在index.html中就可以看到相应的内容了。实际应用中在静态页面中的时候,应当另外指定对应的html文件,如果配置了CDN,那么在本地开发的时候,应该将CDN地址替换为本地地址,这个插件也提供了server选项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server: {
  port: 80,
  proxy: {
    ......
    target: 'http://localhost',
    changeOrigin: true,
    ......
  }
}

3.服务端渲染

服务端渲染是由服务端将渲染好的html字符串直接返给客户端,客户端接受到对应的html字符串后,解析渲染呈现出相应的页面。客户端渲染时,客户端需要去请求相应的接口取得相应的数据然后渲染出html页面,因此,服务端渲染白屏时间相对较短。还有个额外的好处当然是利于SEO,这个作用恐怕还在白屏优化之上。客户端渲染一开始的页面为空,在某些爬虫来爬的时候什么东西都获取不到,所以不利于SEO,当然这是后话了。现在我们这用的比较多的,一般是以nodejs为中间层,接受到客户端的请求后,发送真正的请求到后台,随后在nodejs中处理完对应的数据,渲染页面。现在以reactvue为基础,也有各自对应的服务端渲染方案,如next.jsnuxt.js这里就不介绍了

4.骨架屏

骨架屏可以看做用来代替页面loading效果的一种方案,在页面加载完成之前,呈现出页面的一个大致结构,给用户一个良好的体验,让他觉得这个页面正在缓慢加载,这种效果看起来也比单纯的放一张菊花图、一张加载动画好一些。这个方案更多的是带来更好的用户体验。手写骨架屏,首先确定页面的基本结构,然后按照这个结构写一个类似的加载页面,这个由设计来定。以下介绍使用webpack(4.0+)来写一个简单的骨架屏插件。

首先定义一个骨架屏插件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const HtmlWebpackPlugin = require('html-webpack-plugin')

let Skeleton = function (options) {
  // 接收传入的参数
}

Skeleton.prototype.apply = function (compiler) {
  compiler.plugin('compilation', compilation => {
    HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
      'Skeleton',
      (htmlData, cb) => {
        htmlData.html = htmlData.html.replace('<div id="root"></div>', `骨架屏代码`)
        cb(null, htmlData)
      }
    )
  })
}

module.exports = Skeleton

然后webpack配置文件中引入即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
new HtmlWebPackPlugin({
  template: 'public/index.html',
  filename: 'index.html',
  inject: true
}),
new Skeleton({
  template: 'public/index.html'
})
...

1.以上首先定义一个插件对象Skeleton

2.引入html-webpack-plugin,在html-webpack-plugin的钩子函数beforeEmit中,调用这个Skeleton对象,

beforeEmit这个钩子函数的作用在于html-webpack-plugin插件将生成的html插入模板文件前进行的操作。这里就相当于在生成真正的html代码之前调用生成骨架屏页面。

3.调用cb,传回html内容

4.需要注意的是,replace中的需要替换的根元素、类名不要搞错了。

其余的,根据不同的路径,入口可以将参数传入这个构造函数中,然后判断显示不同的骨架屏。最大的缺点就是复用性不强,也许会随着页面的迭代而经常变动,维护起来也很麻烦。

还有些其他自动生成骨架屏的开源方案。比如饿了么开源的page-skeleton-webpack-plugin

通过 puppeteer 在服务端操控 headless Chrome 打开开发中的需要生成骨架屏的页面,在等待页面加载渲染完成之后,在保留页面布局样式的前提下,通过对页面中元素进行删减或增添,对已有元素通过层叠样式进行覆盖,这样达到在不改变页面布局下,隐藏图片和文字,通过样式覆盖,使得其展示为灰色块。然后将修改后的 HTML 和 CSS 样式提取出来,这样就是骨架屏了

它的大致原理,就是利用puppeteer获取页面结构,将不同的节点分类处理,展现不同的’骨架效果’,最后实际渲染的时候,进行相应的替换。但是就针对于这个项目page-skeleton-webpack-plugin来说,坑比较多,而且不支持webpack4,慎重使用。