在使用 Native APP 时我们经常可以看到在实际内容加载出来之前会有一些 占位图片
,因为其结构和实际加载内容相似(如新闻页面加载之前也会有一个纵向列表),因此让用户感知到自己的页面正在加载中,体验较好。而传统的 Web 站点因为无法实现这一点,在加载前统一为白屏,我们能优化的仅仅是缩短这个 白屏时间
,这就决定了 Web App 相较于 Native APP 在用户体验上的先天劣势。
我们把这个 占位图片
称为 skeleton
。通过 PWA 的缓存机制,我们现在已经有能力让 Skeleton 也出现在 Web App 上取代白屏。在了解具体细节之前,我们先观察一下我的demo效果图。
github地址:https://github.com/hwq888/skeleton
我们希望在构建时渲染 skeleton 组件,将渲染 DOM 插入 html 的挂载点中,同时将使用的样式通过 style 标签内联。这样在前端 JS 渲染完成之前,用户将看到页面的大致骨架,感知到页面是正在加载的。
我们当然可以选择在开发时直接将页面骨架内容写入 html 模版中,但是这会带来两个问题:
下面我们将看看插件在具体实现中是如何解决这两个问题的:
github 地址:https://github.com/lavas-project/vue-skeleton-webpack-plugin
具体实现步骤:
1、我们用vue-cli 直接构建一下项目跑起来(具体怎么构建就不说了)
2、进去当前项目,执行命令 : npm install vue-skeleton-webpack-plugin
3、我们在src目录下创建 Skeleton.vue
4、创建入口文件:entry-skeleton.js
import Vue from 'vue'
import Skeleton from './Skeleton'
export default new Vue({
components: {
Skeleton
},
template: ' '
})
5、我们在build 目录下创建 webpack.skeleton.conf.js
'use strict';
const path = require('path')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const nodeExternals = require('webpack-node-externals')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = merge(baseWebpackConfig, {
target: 'node',
devtool: false,
entry: {
app: resolve('../src/entry-skeleton.js')
},
output: Object.assign({}, baseWebpackConfig.output, {
libraryTarget: 'commonjs2'
}),
externals: nodeExternals({
whitelist: /\.css$/
}),
plugins: []
})
然后在webpack.dev.conf.js和webpack.prod.conf.js分别加入
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin')
// inject skeleton content(DOM & CSS) into HTML
new SkeletonWebpackPlugin({
webpackConfig: require('./webpack.skeleton.conf'),
quiet: true
})
然后就完成了。
延伸:
1、vue-skeleton-webpack-plugin 可以 使用多个 骨架屏 ,具体的可以查看官网地址:https://github.com/lavas-project/vue-skeleton-webpack-plugin
2、Skeleton Screen 样式实现
简单堆砌出元素结构
html
css
.skeleton {
padding: 10px;
}
.skeleton .skeleton-head,
.skeleton .skeleton-title,
.skeleton .skeleton-content {
background: rgb(194, 207, 214);
}
.skeleton-head {
width: 100px;
height: 100px;
float: left;
}
.skeleton-body {
margin-left: 110px;
}
.skeleton-title {
width: 500px;
height: 60px;
}
.skeleton-content {
width: 260px;
height: 30px;
margin-top: 10px;
}
背景动画,html结构相同,修改部分css样式
.skeleton .skeleton-head,
.skeleton .skeleton-title,
.skeleton .skeleton-content {
background: rgb(194, 207, 214);
background-image: linear-gradient(90deg,rgba(255, 255, 255, 0.15) 25%, transparent 25%);
background-size: 20rem 20rem;
animation: skeleton-stripes 1s linear infinite;
}
@keyframes skeleton-stripes {
from {
background-position: 0 0 ;
}
to {
background-position: 20rem 0;
}
}
.skeleton {
padding: 10px;
}
.skeleton .skeleton-head,
.skeleton .skeleton-title,
.skeleton .skeleton-content {
background: rgb(194, 207, 214);
}
.skeleton-head {
width: 100px;
height: 100px;
float: left;
}
.skeleton-body {
margin-left: 110px;
}
.skeleton-title {
width: 500px;
height: 60px;
transform-origin: left;
animation: skeleton-stretch .5s linear infinite alternate;
}
.skeleton-content {
width: 260px;
height: 30px;
margin-top: 10px;
transform-origin: left;
animation: skeleton-stretch .5s -.3s linear infinite alternate;
}
@keyframes skeleton-stretch {
from {
transform: scalex(1);
}
to {
transform: scalex(.3);
}
}