uniapp的骨架屏生成指南

7,339 阅读2分钟

骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。 使用到的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() {

        })
    }
},

如下图,在控制台上可以得到我们想到的节点信息。

微信截图_20211203205846.png

然后再复制粘贴给data中的rectNodes、circleNodes。 skeleton组件基本上就完成了。我们再做下优化,skeleton组件接收父组件传的show值,默认是true,当父组件的数据接口请求完成之后show设置为false。

大功告成,以下的在浏览器端和微信小程序端的骨架屏展示:

微信截图_20211203205906.png

微信截图_20211203205921.png