uniapp实现骨架屏

前言:用户在等待数据渲染的时候,有可能因为网络速度慢,手机硬件等问题,造成等待时间延长,使得用户体验不好。

之前的做法是放个加载中的图标,而现在是直接根据页面原有元素绘制图形的方式,让用户有种页面就快渲染好的错觉。

参考资料:

https://ext.dcloud.net.cn/plugin?id=256

备注:我是准备应用到项目中,从uniapp的插件市场下载了demo,结果出现一些小问题,在下载下来的demo做了些小修改

加载过程效果图:如图,从图一到图二,最底部多出了一个动态加载的骨架,模拟同一页面多个数据请求(每个请求所需时间不同),

  我这边的处理是在每个请求的回调中,先赋值渲染的动态数据,再重新抓取需要绘制的动态元素(因为绘制的元素需要先有数据给它撑开),

  最后页面中的请求基本完成的时候,隐藏骨架屏,显示原先的页面

问题:对demo有更好建议的可以提出来哈,相互学习一下

代码如下:

组件

  1 <template>
  2     <view v-if="show" :style="{width: systemInfo.width + 'px', height: systemInfo.height + 'px', backgroundColor: bgcolor, position: 'absolute', left: 0, top: 0, zIndex: 9998, overflow: 'hidden'}">
  3         <view v-for="(item,rect_idx) in skeletonRectLists" :key="rect_idx + 'rect'" :class="[loading == 'chiaroscuro' ? 'chiaroscuro' : '']" 
  4         :style="{width: item.width + 'px', height: item.height + 'px', backgroundColor: 'rgb(194, 207, 214)', position: 'absolute', left: item.left + 'px', top: item.top + 'px'}"></view>
  5         <view v-for="(item,circle_idx) in skeletonCircleLists" :key="circle_idx + 'circle'" :class="loading == 'chiaroscuro' ? 'chiaroscuro' : ''" 
  6         :style="{width: item.width + 'px', height: item.height + 'px', backgroundColor: 'rgb(194, 207, 214)', borderRadius: item.width + 'px', position: 'absolute', left: item.left + 'px', top: item.top + 'px'}"></view>
  7         <view class="spinbox" v-if="loading == 'spin'">
  8             <view class="spin"></view>
  9         </view>
 10     </view>
 11 </template>
 12 
 13 <script>
 14     export default {
 15         name: "skeleton",
 16         props: {
 17             bgcolor: {
 18                 type: String,
 19                 value: '#FFF'
 20             },
 21             selector: {
 22                 type: String,
 23                 value: 'skeleton'
 24             },
 25             loading: {
 26                 type: String,
 27                 value: 'spin'
 28             },
 29             show: {
 30                 type: Boolean,
 31                 value: false
 32             },
 33             isNodes: {
 34                 type: Number,
 35                 value: false
 36             } //控制什么时候开始抓取元素节点,只要数值改变就重新抓取
 37         },
 38         data() {
 39             return {
 40                 loadingAni: ['spin', 'chiaroscuro'],
 41                 systemInfo: {},
 42                 skeletonRectLists: [],
 43                 skeletonCircleLists: []
 44             }
 45         },
 46         watch: {
 47             isNodes (val) {
 48                 this.readyAction();
 49             }
 50         },
 51         mounted() {
 52             this.attachedAction();
 53         },
 54         methods: {
 55             attachedAction: function(){
 56                 //默认的首屏宽高,防止内容闪现
 57                 const systemInfo = uni.getSystemInfoSync();
 58                 this.systemInfo = {
 59                     width: systemInfo.windowWidth,
 60                     height: systemInfo.windowHeight
 61                 };
 62                 this.loading = this.loadingAni.includes(this.loading) ? this.loading : 'spin';
 63             },
 64             readyAction: function(){
 65                 console.log('子组件readyAction')
 66                 const that = this;
 67                 //绘制背景
 68                 uni.createSelectorQuery().selectAll(`.${this.selector}`).boundingClientRect().exec(function(res){
 69                     that.systemInfo.height = res[0][0].height + res[0][0].top;
 70                 });
 71                 
 72                 //绘制矩形
 73                 this.rectHandle();
 74 
 75                 //绘制圆形
 76                 this.radiusHandle();
 77             },
 78             rectHandle: function(){
 79                 const that = this;
 80 
 81                 //绘制不带样式的节点
 82                 uni.createSelectorQuery().selectAll(`.${this.selector}-rect`).boundingClientRect().exec(function(res){
 83                     that.skeletonRectLists = res[0];
 84                 });
 85 
 86             },
 87             radiusHandle(){
 88                 const that = this;
 89 
 90                 uni.createSelectorQuery().selectAll(`.${this.selector}-radius`).boundingClientRect().exec(function(res){
 91                     that.skeletonCircleLists = res[0];
 92                 });
 93             }
 94         }
 95     }
 96 </script>
 97 
 98 <style>
 99 .spinbox{
100   position: fixed;
101   display: flex;
102   justify-content: center;
103   align-items: center;
104   height: 100%;
105   width: 100%;
106   z-index: 9999
107 }
108 .spin {
109   display: inline-block;
110   width: 64rpx;
111   height: 64rpx;
112 }
113 .spin:after {
114   content: " ";
115   display: block;
116   width: 46rpx;
117   height: 46rpx;
118   margin: 1rpx;
119   border-radius: 50%;
120   border: 5rpx solid #409eff;
121   border-color: #409eff transparent #409eff transparent;
122   animation: spin 1.2s linear infinite;
123 }
124 @keyframes spin {
125   0% {
126     transform: rotate(0deg);
127   }
128   100% {
129     transform: rotate(360deg);
130   }
131 }
132 
133 .chiaroscuro{
134   width: 100%;
135   height: 100%;
136   background: rgb(194, 207, 214);
137   animation-duration: 2s;
138   animation-name: blink;
139   animation-iteration-count: infinite;
140 }
141 
142 @keyframes blink {
143   0% {
144     opacity: .4;
145   }
146   50% {
147     opacity: 1;
148   }
149   100% {
150     opacity: .4;
151   }
152 }
153 
154 @keyframes flush {
155   0% {
156     left: -100%;
157   }
158   50% {
159     left: 0;
160   }
161   100% {
162     left: 100%;
163   }
164 }
165 .shine {
166   animation: flush 2s linear infinite;
167   position: absolute;
168   top: 0;
169   bottom: 0;
170   width: 100%;
171   background: linear-gradient(to left,
172   rgba(255, 255, 255, 0) 0%,
173   rgba(255, 255, 255, .85) 50%,
174   rgba(255, 255, 255, 0) 100%
175   )
176 }
177 </style>
View Code

页面demo

  1 <template>
  2     <view class="controller">
  3         <view class="container skeleton" :style="{visibility: showSkeleton ? 'hidden' : 'visible'}">
  4             <view class="userinfo">
  5                 <block>
  6                     <!--skeleton-radius 绘制圆形-->
  7                     <image class="userinfo-avatar skeleton-radius" :src="userInfo.avatarUrl" mode="cover"></image>
  8                      <!--skeleton-rect 绘制矩形-->
  9                     <text class="userinfo-nickname skeleton-rect">{{userInfo.nickName}}</text>
 10                 </block>
 11             </view>
 12             <view style="margin: 20px 0">
 13                 <view v-for="(item,index) in lists" :key="index" class="lists">
 14                     <text class="skeleton-rect">{{item}}</text>
 15                 </view>
 16             </view>
 17             <view class="usermotto">
 18                 <text class="user-motto skeleton-rect">{{motto}}</text>
 19             </view>
 20         </view>
 21         <!--引用组件-->
 22         <skeleton :show="showSkeleton" :isNodes="isNodes" ref="skeleton" loading="chiaroscuro" selector="skeleton" bgcolor="#FFF"></skeleton>
 23     </view>
 24 </template>
 25 
 26 <script>
 27     //引入骨架屏组件(以我本地地址为例,具体地址由自身引用位置决定)
 28     import skeleton from "@/components/quick-skeleton/quick-skeleton.vue";
 29     export default {
 30         data() {
 31             return {
 32                 motto: '',
 33                 userInfo: {
 34                     avatarUrl: 'https://wx.qlogo.cn/mmopen/vi_32/s4RzXCAQsVNliaJXtHBvdpAkeRwnK7Jhiaf9mzuVqEhZza3zSYM7tJ1xZCQE9SCoOR8qjVEjDKltw1SQnxyicWq6A/132',
 35                     nickName: 'jayzou'
 36                 },
 37                 // lists: [
 38                 //     '第1行数据',
 39                 //     '第2行数据',
 40                 //     '第3行数据',
 41                 //     '第4行数据',
 42                 //     '第5行数据',
 43                 //     '第6行数据'
 44                 // ],
 45                 lists: [], //如果没有默认数据
 46                 showSkeleton: true,  //骨架屏显示隐藏
 47                 isNodes: 0 //控制什么时候开始抓取元素节点,只要数值改变就重新抓取
 48             };
 49         },
 50         components: {
 51             skeleton
 52         },
 53         onLoad: function () {
 54             const that = this;
 55             
 56             //问题:骨架屏出现的时间段,部分已经渲染完毕,但还是得等骨架屏隐藏才一起出现
 57             
 58             setTimeout(() => {
 59                 this.lists = [
 60                     '第1行数据',
 61                     '第2行数据',
 62                     '第3行数据',
 63                     '第4行数据',
 64                     '第5行数据',
 65                     '第6行数据'
 66                 ]
 67                 that.isNodes ++;
 68             }, 182);
 69             
 70             setTimeout(() => {
 71                 that.motto = 'Hello World'
 72                 that.isNodes ++;
 73             }, 500);
 74             
 75             setTimeout(() => {
 76                 that.showSkeleton = false;
 77             }, 2000);
 78         },
 79         /**
 80          *  页面载入完成后调用子组件的方法生成预加载效果
 81          */
 82         onReady:function(){
 83             
 84         }
 85     }
 86 </script>
 87 
 88 <style>
 89 .container {
 90     padding: 20upx 60upx;
 91 }
 92 /**index.wxss**/
 93 .userinfo {
 94   display: flex;
 95   flex-direction: column;
 96   align-items: center;
 97 }
 98 .userinfo-avatar {
 99   width: 128rpx;
100   height: 128rpx;
101   margin: 20rpx;
102   border-radius: 50%;
103 }
104 .userinfo-nickname {
105   color: #aaa;
106 }
107 .usermotto {
108   margin-top: 200px;
109 }
110 .lists{
111   margin: 10px 0;
112 }
113 .list{
114   margin-right: 10px;
115 }
116 </style>
View Code

 

posted @ 2020-04-23 15:43  光*  阅读(7947)  评论(0编辑  收藏  举报