骨架屏的几种方案
背景:
解决首页首屏加载慢或加载时出现白屏问题,以前的方案首页首屏可视区域显示的加载,要么懒加载,要么loading一个gif的小图,要么预加载部分内容等。
何为骨架屏
简单来说,骨架屏就是在页面内容未加载完成的时候,先使用一些图形进行占位,待内容加载完成之后再把它替换掉。相对传统加载方案,skelteton方案 用户体验感增强。
方案一、taro-plugin-skeleton
配合index.config使用 单独写骨架屏页面,与原有业务耦合度较低。 当页面渲染固定元素,骨架屏会被遮挡 原理:拦截原有小程序底层页面,使用骨架屏页面替换。 当小程序View 没有渲染的时候展示骨架屏,小程序View展示会遮挡住骨架屏页面。
缺点: 上拉下拉会露出骨架屏页面。 骨架屏页面需要单独写样式。 此方案无项目实践,感兴趣的朋友可自己玩玩
方案二、taro-skeleton
骨架屏组件,封装了基础样式 可以根据业务定制骨架屏。也可以仅针对首页设置,下面案例二有针对taro 小程序的骨架屏配置及效果
缺点: 与业务耦合度较高,需要单独写骨架屏逻辑。
方案三、page-skeleton-webpack-plugin
自动生成骨架屏,H5项目建议使用此方案 原理: 依赖 html-webpack-plugin 对html进行计算,替换dom。
案例一
skeleton 技术主要使用在以内容为主的APP和网页应用较多,接下来以VUE工程为例,在基于Vue的SPA项目中实现骨架屏。
分析Vue页面的内容加载过程
初始化一个vue-cli项目
npm install --global vue-cli vue-cli@3.0
vue create my-project
cd my-project
npm install
npm run serve 或 yarn run serve
执行npm run seve 命令后,先看下index.html 写的具体内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico"> -->
<title>jingbo</title>
</head>
<body>
<div id="app">
<!-- built files will be auto injected -->
<script src="iconfont.js" type="text/javascript"></script>
</body>
</html>
可以看到,DOM里面有且仅有一个div#app
,当js被执行完成之后,此div#app
会被整个替换掉,因此,我们可以来做一下实验,在此div里面添加一些内容:
<div id="app">
<p>Hello skeleton</p>
<p>Hello skeleton</p>
<p>Hello skeleton</p>
</div>
打开chrome的开发者工具,在Network
里面找到Online功能,调节网速为“Slow 3G”,刷新页面,就能看到页面先是展示了三句“Hello skeleton”,待js加载完了才会替换为原本要展示的内容
也就是说,我们只要在inde.htm文件中 div#app
内直接插入骨架替换即可。
这里补充一个小插曲: 因使用的element-ui,需要安装Node-sass,国内镜像的问题node-sass就像是狗皮膏药,挺烦的,这里提供一种离线命令安装方式 在packsge.json,script中添加preinstall指令;配置如下
"scripts": {
"serve": "vue-cli-service serve --mode dev",
"serve:test": "vue-cli-service serve --mode test",
"serve:qa": "vue-cli-service serve --mode qa",
"serve:stage": "vue-cli-service serve --mode stage",
"serve:prod": "vue-cli-service serve --mode prod",
"build": "vue-cli-service build --mode dev",
"build:test": "vue-cli-service build --mode test",
"build:qa": "vue-cli-service build --mode qa",
"build:stage": "vue-cli-service build --mode stage",
"build:prod": "vue-cli-service build --mode prod",
"lint": "vue-cli-service lint",
"skeleton": "webpack --config ./skeleton/webpack.skeleton.conf.js && node ./skeleton/skeleton.js",
"analyz": "build for production and view the bundle analyzer report",
"mock": "json-server --watch mock/db.json",
"preinstall": "node -p \"[process.platform, process.arch, process.versions.modules].join('-')\" "
},
在下载之前一定要弄清楚你的服务器环境,要和你下载的离线包一致。npm i 时可以看到 看到了你的配置,我的是win32-x64-93
包安装后在node_modules下找到node-sass看有没有对应的vendor文件夹
实现
显然,如果手动在工程中inde.html文件中 div#app
里面写入骨架屏内容是不科学的,我们需要一个扩展性强且自动化的易维护方案。既然是在Vue项目里,我们当然希望所谓的骨架屏也是一个.vue
文件,它能够在构建时由工具自动注入到div#app
里面。
接下来,我们在项目中创建skeleton文件夹,分别添加
Skeleton.vue文件
skeleton.entry.js
入口文件
完成上述文件创建后,需要安装个关键插件vue-server-renderer
。该插件本用于服务端渲染,但是项目中主要利用它能够把.vue
文件处理成html
和css
字符串的功能,来完成骨架屏的注入,流程如下:
根据流程图,我们还需要在skeleton文件夹新建一个webpack.skeleton.conf.js
文件,以专门用来进行骨架屏的构建。
详见工程文件配置
module.exprot = {
target: 'node',
externals: nodeExternals({
whitelist: /.css$/
}),
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
plugins: [
new VueSSRServerPlugin({
filename: 'skeleton.json'
})
]
}
该配置文件和普通的配置文件基本完全一致,主要的区别在于其target: 'node'
,配置了externals
,以及在plugins
里面加入了VueSSRServerPlugin
。在VueSSRServerPlugin
中,指定了其输出的json文件名。我们可以通过运行下列指令,在/dist
目录下生成一个skeleton.json
文件:
webpack --config ./webpack.skeleton.conf.js
这个文件在记载了骨架屏的内容和样式,会提供给
vue-server-renderer
使用。
接下来,在skeleton文件夹下新建一个skeleton.js
,该文件即将被用于往index.html
内插入骨架屏。
/*
* @Descripttion:
* @Author: lzx
* @Date: 2019-04-11 11:01:03
* @LastEditors : lzx
* @LastEditTime : 2019-12-27 12:58:55
*/
const fs = require('fs')
const htmlMinifier = require('html-minifier')
const {
resolve
} = require('path')
const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
// 读取`skeleton.json`,以`index.template.html`为模板写入内容
const renderer = createBundleRenderer(resolve(__dirname, './dist/skeleton.json'), {
template: fs.readFileSync(resolve(__dirname, '../public/index.template.html'), 'utf-8')
})
// 把上一步模板完成的内容写入(替换)`index.html`
renderer.renderToString({}, (err, html) => {
console.log(err)
html = html.replace(/&%##/g, '<%= BASE_URL %>')
html = htmlMinifier.minify(html, { // 将html内容压缩
collapseWhitespace: true,
minifyCSS: true
})
fs.writeFileSync('./public/index.html', html, 'utf-8')
})
注:为了区分 工程文件的index.html文件和骨架屏index.html文件(index.template.html)
作为骨架屏模板的index.template.html文件,需要在被写入内容的位置添加<!--vue-ssr-outlet-->占位符,
<div id="app">
<!-- 作为模板的html文件,需要在被写入内容的位置添加<vue-ssr-outlet>占位符, -->
<!--vue-ssr-outlet-->
</div>
最后执行
执行命令:node skeleton.js 就可完成骨架屏的注入。
`或在package.json文件中添加
"script": {
"skeleton": "webpack --config ./skeleton/webpack.skeleton.conf.js && node ./skeleton/skeleton.js",
}
执行命令 npm run skeleton 即可
最终效果预览
注意事项
skeleton.vue骨架文件根据不同的项目要求,可自定义展示dom结构及布局
案例二
基于taro 小程序的骨架方案
app.js入口文件引入css
import 'taro-skeleton/dist/index.css'; // 引入骨架屏组件样式
首页面引入
import Skeleton from 'taro-skeleton';
使用
const [isLoading3, setIsLoading3] = useState(false) // 是否显示 taro-skeleton
const getHotGoods = () => {
const param = {
url: `v1/ext/item/getPopularItem`,
method: 'get',
data: Taro.getStorageSync('userInfo').salesmanTob === true ? {
userId: Taro.getStorageSync('customerInfo').userId,
isGroupBuy: Taro.getStorageSync('isGroupBuy') || false,
} : {}
}
Request(param).then(res => {
if (res.resultCode === '0') {
if (res.data && res.data.length > 0) {
setIsLoading3(false)
}
}
})
}
<View className='classGoods'>
<GoodsTop topParam={{ title: '爆款推荐' }} getMore={() => getGoodsMore('stat')} />
<Skeleton loading={isLoading3} avatar title row={3} avatarShape='square' avatarSize='96px' action >
<Goods data={hotGoodsList} ></Goods>
</Skeleton>
</View>
转载自:https://juejin.cn/post/7248509579125424189