骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。 使用到的API有uni.createSelectorQuery()uni.getSystemInfoSync()。
常规首页的布局
一般而言,我们的首页的基础布局是包含的有:顶部搜索、轮播、金刚区、新闻简报、活动魔方。
<template>
<view class="content">
<!-- 顶部搜索 -->
<headerSerch></headerSerch>
<!-- 轮播 -->
<swiperBg></swiperBg>
<!-- 金刚区 -->
<menus></menus>
<!-- 新闻简报 -->
<news></news>
<!-- 活动魔方 -->
<activity></activity>
<!-- 骨架屏 -->
<skeleton :show="show"></skeleton>
</view>
</template>
<script>
import headerSerch from './components/headerSerch.vue'
import swiperBg from './components/swiperBg.vue'
import menus from './components/menus.vue'
import news from './components/news.vue'
import activity from './components/activity.vue'
import skeleton from './components/skeleton.vue'
export default {
components: {
headerSerch,
swiperBg,
menus,
news,
activity,
skeleton
},
data() {
return {
show: true
}
},
mounted() {
setTimeout(()=>{
this.show = false
},1200)
}
}
</script>
<style scoped>
</style>
skeleton组件的实现
代码如下,稍后给大家解释
<template>
<!-- 骨架屏的高度宽度和背景,用绝对定位提高层级 -->
<view v-if="show">
<view :style="{
width: windowWidth,
height: windowHeight,
backgroundColor: bgColor,
position: 'absolute',
zIndex: 9999,
top: top,
left: left
}">
<view v-for="(item,index) in rectNodes" :key="index + 'Rect'" class="skeleton-fade" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
position: 'absolute',
top: item.top + 'px',
left: item.left + 'px'
}">
</view>
<view v-for="(item,index) in circleNodes" :key="index" class="skeleton-fade" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
borderRadius: item.width/2 + 'px',
position: 'absolute',
top: item.top + 'px',
left: item.left + 'px'
}">
</view>
</view>
</view>
</template>
<script>
let systemInfo = uni.getSystemInfoSync();
export default {
name: 'skeleton',
props: {
show: {
type: Boolean,
default: true
}
},
data() {
return {
windowWidth: systemInfo.windowWidth + 'px',
windowHeight: systemInfo.windowHeight + 'px',
bgColor: '#fff',
elColor: '#e5e5e5',
top: 0,
left: 0,
borderRadius: 10,
rectNodes: [{
"id": "",
"dataset": {},
"left": 15,
"right": 87,
"top": 4,
"bottom": 30.5,
"width": 72,
"height": 32
}, {
"id": "",
"dataset": {},
"left": 102,
"right": 360.20001220703125,
"top": 4,
"bottom": 32,
"width": 258.20001220703125,
"height": 32
}, {
"id": "",
"dataset": {},
"left": 15,
"right": 360.20001220703125,
"top": 46,
"bottom": 171,
"width": 345.20001220703125,
"height": 125
}, {
"id": "",
"dataset": {},
"left": 15,
"right": 360.20001220703125,
"top": 343,
"bottom": 373,
"width": 345.20001220703125,
"height": 30
}, {
"id": "",
"dataset": {},
"left": 15,
"right": 183,
"top": 383,
"bottom": 533,
"width": 168,
"height": 150
}, {
"id": "",
"dataset": {},
"left": 188.1999969482422,
"right": 360.1999969482422,
"top": 384,
"bottom": 457,
"width": 172,
"height": 73
}, {
"id": "",
"dataset": {},
"left": 188.1999969482422,
"right": 360.1999969482422,
"top": 459,
"bottom": 532,
"width": 172,
"height": 73
}],
circleNodes: [{
"id": "",
"dataset": {},
"left": 27.012500762939453,
"right": 72.01250076293945,
"top": 184,
"bottom": 229,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 96.05000305175781,
"right": 141.0500030517578,
"top": 184,
"bottom": 229,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 165.08750915527344,
"right": 210.08750915527344,
"top": 184,
"bottom": 229,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 234.125,
"right": 279.125,
"top": 184,
"bottom": 229,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 303.1625061035156,
"right": 348.1625061035156,
"top": 184,
"bottom": 229,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 27.012500762939453,
"right": 72.01250076293945,
"top": 265,
"bottom": 310,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 96.05000305175781,
"right": 141.0500030517578,
"top": 265,
"bottom": 310,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 165.08750915527344,
"right": 210.08750915527344,
"top": 265,
"bottom": 310,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 234.125,
"right": 279.125,
"top": 265,
"bottom": 310,
"width": 45,
"height": 45
}, {
"id": "",
"dataset": {},
"left": 303.1625061035156,
"right": 348.1625061035156,
"top": 265,
"bottom": 310,
"width": 45,
"height": 45
}]
}
},
mounted() {
// 矩形骨架元素
this.getRectEls();
// 圆形骨架元素
this.getCircleEls();
},
methods: {
getRectEls() {
let query = uni.createSelectorQuery().in(this.$parent)
query.selectAll('.skeleton-rect').boundingClientRect(res => {
console.log('rect', JSON.stringify(res));
}).exec(function() {
})
},
getCircleEls() {
let query = uni.createSelectorQuery().in(this.$parent)
query.selectAll('.skeleton-circle').boundingClientRect(res => {
console.log('circle', JSON.stringify(res));
}).exec(function() {
})
}
},
}
</script>
<style>
.skeleton-fade {
width: 100%;
height: 100%;
background: rgb(194, 207, 214);
animation-duration: 1.5s;
animation-name: blink;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes blink {
0% {
opacity: .4;
}
50% {
opacity: 1;
}
100% {
opacity: .4;
}
}
</style>
步骤一 设置骨架屏的基础样式
我们通过绝对定位的方式把组件的根元素提高层级,避免被父组件的其他组件覆盖掉。使用 uni.getSystemInfoSync()同步获取系统的可使用窗口宽度和可使用窗口高度并赋值给组件根元素的宽高。
<view :style="{
width: windowWidth,
height: windowHeight,
backgroundColor: bgColor,
position: 'absolute',
zIndex: 9999,
top: top,
left: left
}">
......
......
</view>
<script>
let systemInfo = uni.getSystemInfoSync();
export default {
name: 'skeleton',
props: {
show: {
type: Boolean,
default: true
},
},
data() {
return {
windowWidth: systemInfo.windowWidth + 'px',
windowHeight: systemInfo.windowHeight + 'px',
bgColor: '#fff',
top: 0,
left: 0,
}
}
}
</script>
步骤二 渲染出占位的灰色块
通过uniapp的uni.createSelectorQuery()接口,查询页面带有指定类名的元素的位置和尺寸, 通过绝对定位的方式,用同样尺寸的灰色块定位到相同的位置。
在骨架屏中多数用的主要的矩形节点rectNodes 和圆形节点circleNodes。
首先给这些元素加上相同的skeleton-fade类,这个类的主要为了有一个灰色的背景并使用animate属性使其看到颜色的深浅变化。
<view :style="{
width: windowWidth,
height: windowHeight,
backgroundColor: bgColor,
position: 'absolute',
zIndex: 9999,
top: top,
left: left
}">
<view v-for="(item,index) in rectNodes" :key="index + 'Rect'" class="skeleton-fade" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
position: 'absolute',
top: item.top + 'px',
left: item.left + 'px'}">
</view>
<view v-for="(item,index) in circleNodes" :key="index" class="skeleton-fade" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
borderRadius: item.width/2 + 'px',
position: 'absolute',
top: item.top + 'px',
left: item.left + 'px'}">
</view>
</view>
<script>
let systemInfo = uni.getSystemInfoSync();
export default {
name: 'skeleton',
props: {
show: {
type: Boolean,
default: true
},
},
data() {
return {
windowWidth: systemInfo.windowWidth + 'px',
windowHeight: systemInfo.windowHeight + 'px',
bgColor: '#fff',
top: 0,
left: 0,
rectNodes: [],
circleNodes: []
}
}
}
</script>
<style>
.skeleton-fade {
width: 100%;
height: 100%;
background: rgb(194, 207, 214);
animation-duration: 1.5s;
animation-name: blink;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes blink {
0% {
opacity: .4;
}
50% {
opacity: 1;
}
100% {
opacity: .4;
}
}
</style>
按照官方的API使用说明,我们得在mounted 后进行调用方法。
在uni.createSelectorQuery()的后面加in(this.$parent)在微信小程序才能生效,在H5端不用加也生效。(我们主要是获取指定元素的位置和高度详细并赋值给rectNodes、circleNodes,所以得到之后可以把这两个方法删掉。)
mounted() {
// 矩形骨架元素
this.getRectEls();
// 圆形骨架元素
this.getCircleEls();
},
methods: {
getRectEls() {
let query = uni.createSelectorQuery().in(this.$parent)
query.selectAll('.skeleton-rect').boundingClientRect(res => {
console.log('rect', JSON.stringify(res));
}).exec(function() {
})
},
getCircleEls() {
let query = uni.createSelectorQuery().in(this.$parent)
query.selectAll('.skeleton-circle').boundingClientRect(res => {
console.log('circle', JSON.stringify(res));
}).exec(function() {
})
}
},
如下图,在控制台上可以得到我们想到的节点信息。
然后再复制粘贴给data中的rectNodes、circleNodes。 skeleton组件基本上就完成了。我们再做下优化,skeleton组件接收父组件传的show值,默认是true,当父组件的数据接口请求完成之后show设置为false。
大功告成,以下的在浏览器端和微信小程序端的骨架屏展示: