白屏时间指的是从用户进入网站,一直到页面有内容展示出来的时间节点,在这期间用户什么东西都看不到,这个过程包括dns查询、建立tcp连接、发送首个http请求、返回html文档、html文档head解析完毕。白屏时间的长短,影响着用户的体验,白屏时间过长,用户失去了耐心等待,也许就关闭了网页
一份是 Akamai 的研究报告,当时总共采访了大约 1048 名网上购物者,得出了这样的结论:
- 大约有 47% 的用户期望他们的页面在两秒之内加载完成。
- 如果页面加载时间超过 3s,大约有 40% 的用户选择离开或关闭页面。
白屏时间的计算就如上诉定义说的那样,只需要记录用户进入页面的开始时间,记录head加载完成的时间,记录两者的差值,这就是所说的白屏时间。计算的时候可以分别获取两个时间,计算他们的差,也可以使用performance.mark,在开始地点标记或者说打点,在结束地点获取这个entry
,拿到它的duration
。
<!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
属性代表发送请求前的时间,两者之差也可以计算白屏时间
对于具体的时间优化,可以针对整个页面渲染的过程来处理。这里说一下:预渲染
、服务端渲染
、使用骨架屏
,这也可以说是用户的体验优化
首先预渲染,主要是用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
配置文件中添加这个插件的配置
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
为例
export default {
mounted() {
document.dispatchEvent(new Event('render-event'));
}
};
然后访问对应的route
,在index.html
中就可以看到相应的内容了。实际应用中在静态页面中的时候,应当另外指定对应的html
文件,如果配置了CDN
,那么在本地开发的时候,应该将CDN
地址替换为本地地址,这个插件也提供了server
选项
server: {
port: 80,
proxy: {
......
target: 'http://localhost',
changeOrigin: true,
......
}
}
服务端渲染
服务端渲染是由服务端将渲染好的html
字符串直接返给客户端,客户端接受到对应的html
字符串后,解析渲染呈现出相应的页面。客户端渲染时,客户端需要去请求相应的接口取得相应的数据然后渲染出html
页面,因此,服务端渲染白屏时间相对较短。还有个额外的好处当然是利于SEO,这个作用恐怕还在白屏优化之上。客户端渲染一开始的页面为空,在某些爬虫来爬的时候什么东西都获取不到,所以不利于SEO,当然这是后话了。现在我们这用的比较多的,一般是以nodejs
为中间层,接受到客户端的请求后,发送真正的请求到后台,随后在nodejs
中处理完对应的数据,渲染页面。现在以react
、vue
为基础,也有各自对应的服务端渲染方案,如next.js
、nuxt.js
这里就不介绍了
骨架屏
骨架屏可以看做用来代替页面loading
效果的一种方案,在页面加载完成之前,呈现出页面的一个大致结构,给用户一个良好的体验,让他觉得这个页面正在缓慢加载,这种效果看起来也比单纯的放一张菊花图、一张加载动画好一些。这个方案更多的是带来更好的用户体验。手写骨架屏,首先确定页面的基本结构,然后按照这个结构写一个类似的加载页面,这个由设计来定。以下介绍使用webpack(4.0+)
来写一个简单的骨架屏插件。
首先定义一个骨架屏插件
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
配置文件中引入即可
...
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
,慎重使用。