基于uni-app的一个骨架屏插件。
在使用的时候可以直接在components中引入组件quick-skeleton.vue。组件代码如下:
1 <template> 2 <view 3 v-show="show" 4 :style="{ 5 width: systemInfo.width + 'px', 6 height: systemInfo.height + 'px', 7 backgroundColor: bgcolor, 8 position: 'absolute', 9 left: 0, 10 top: 0, 11 zIndex: 9998, 12 overflow: 'hidden' 13 }" 14 > 15 <view 16 v-for="(item, rect_idx) in skeletonRectLists" 17 :key="rect_idx + 'rect'" 18 :class="[loading == 'chiaroscuro' ? 'chiaroscuro' : '']" 19 :style="{ 20 width: item.width + 'px', 21 height: item.height + 'px', 22 backgroundColor: '#f4f4f', 23 position: 'absolute', 24 left: item.left + 'px', 25 top: item.top + 'px' 26 }" 27 ></view> 28 <view 29 v-for="(item, circle_idx) in skeletonCircleLists" 30 :key="circle_idx + 'circle'" 31 :class="loading == 'chiaroscuro' ? 'chiaroscuro' : ''" 32 :style="{ 33 width: item.width + 'px', 34 height: item.height + 'px', 35 backgroundColor: '#f4f4f', 36 borderRadius: item.width + 'px', 37 position: 'absolute', 38 left: item.left + 'px', 39 top: item.top + 'px' 40 }" 41 ></view> 42 43 <view class="spinbox" v-if="loading == 'spin'"><view class="spin"></view></view> 44 </view> 45 </template> 46 47 <script> 48 export default { 49 name: 'skeleton', 50 props: { 51 bgcolor: { 52 type: String, 53 value: '#FFF' 54 }, 55 selector: { 56 type: String, 57 value: 'skeleton' 58 }, 59 loading: { 60 type: String, 61 value: 'spin' 62 }, 63 show: { 64 type: Boolean, 65 value: false 66 } 67 }, 68 data() { 69 return { 70 loadingAni: ['spin', 'chiaroscuro'], 71 systemInfo: {}, 72 skeletonRectLists: [], 73 skeletonCircleLists: [] 74 }; 75 }, 76 watch: { 77 show() { 78 this.attachedAction(); 79 this.readyAction(); 80 } 81 }, 82 methods: { 83 attachedAction: function() { 84 //默认的首屏宽高,防止内容闪现 85 const systemInfo = uni.getSystemInfoSync(); 86 this.systemInfo = { 87 width: 750, 88 height: 1440 89 }; 90 this.loading = this.loadingAni.includes(this.loading) ? this.loading : 'spin'; 91 }, 92 readyAction: function() { 93 const that = this; 94 //绘制背景 95 uni.createSelectorQuery() 96 .selectAll(`.${this.selector}`) 97 .boundingClientRect() 98 .exec(function(res) { 99 that.systemInfo.height = res[0][0].height + res[0][0].top; 100 }); 101 102 //绘制矩形 103 this.rectHandle(); 104 105 //绘制圆形 106 this.radiusHandle(); 107 }, 108 rectHandle: function() { 109 const that = this; 110 111 //绘制不带样式的节点 112 uni.createSelectorQuery() 113 .selectAll(`.${this.selector}-rect`) 114 .boundingClientRect() 115 .exec(function(res) { 116 that.skeletonRectLists = res[0]; 117 }); 118 }, 119 radiusHandle() { 120 const that = this; 121 122 uni.createSelectorQuery() 123 .selectAll(`.${this.selector}-radius`) 124 .boundingClientRect() 125 .exec(function(res) { 126 that.skeletonCircleLists = res[0]; 127 }); 128 } 129 } 130 }; 131 </script> 132 133 <style> 134 .spinbox { 135 position: fixed; 136 display: flex; 137 justify-content: center; 138 align-items: center; 139 height: 100%; 140 width: 100%; 141 z-index: 9999; 142 } 143 .spin { 144 display: inline-block; 145 width: 64rpx; 146 height: 64rpx; 147 } 148 .spin:after { 149 content: ' '; 150 display: block; 151 width: 46rpx; 152 height: 46rpx; 153 margin: 1rpx; 154 border-radius: 50%; 155 border: 5rpx solid #409eff; 156 border-color: #409eff transparent #409eff transparent; 157 animation: spin 1.2s linear infinite; 158 } 159 @keyframes spin { 160 0% { 161 transform: rotate(0deg); 162 } 163 100% { 164 transform: rotate(360deg); 165 } 166 } 167 168 .chiaroscuro { 169 width: 100%; 170 height: 100%; 171 background: #f4f4f4; 172 animation-duration: 2s; 173 animation-name: blink; 174 animation-iteration-count: infinite; 175 } 176 177 @keyframes blink { 178 0% { 179 opacity: 0.5; 180 } 181 50% { 182 opacity: 1; 183 } 184 100% { 185 opacity: 0.5; 186 } 187 } 188 189 @keyframes flush { 190 0% { 191 left: -100%; 192 } 193 50% { 194 left: 0; 195 } 196 100% { 197 left: 100%; 198 } 199 } 200 .shine { 201 animation: flush 2s linear infinite; 202 position: absolute; 203 top: 0; 204 bottom: 0; 205 width: 100%; 206 background: linear-gradient(to left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.85) 50%, rgba(255, 255, 255, 0) 100%); 207 } 208 </style>
引入代码后,在需要加载骨架屏的页面中,为整个页面的盒子加一类名skeleton,并加一个兄弟节点quick-skeleton引入组件:
showSkeleton控制骨架屏是否显示;
骨架屏显示时catchtouchmove和fixed控制页面是否可以滑动,这里用fixed让页面不可滑动;
bgcolor为骨架屏的背景颜色。其余属性不需改动,可以直接使用。
在onReady()中控制骨架屏的加载,
若是即时加载的骨架屏,可以将_this.showSkeleton = false;放在接口调用成功之后,这样就实现了加载完后骨架屏自动消失的效果,这里暂用定时器来实现骨架屏的隐藏。
1 <view> 2 <quick-skeleton 3 :show="showSkeleton" 4 ref="skeleton" 5 catchtouchmove="true" 6 fixed="true" 7 loading="chiaroscuro" 8 selector="skeleton" 9 bgcolor="#FFF" 10 style="overflow: hidden;" 11 ></quick-skeleton> 12 <div class="page-content skeleton"></div> 13 </view> 14 <script> 15 import quickSkeleton from '../../components/quick-skeleton.vue'; 16 export default { 17 data() { 18 return { 19 showSkeleton: true, 20 }; 21 }, 22 components: { 23 quickSkeleton 24 }, 25 onReady() { 26 let _this = this; 27 _this.$refs.skeleton.attachedAction(); 28 _this.$refs.skeleton.readyAction(); 29 setTimeout(function() { 30 _this.showSkeleton = false; 31 }, 3000); 32 }, 33 } 34 </script>
然后只要在页面中为想要生成骨架屏的元素加类名即可:
skeleton-radius表示圆形,skeleton-rect表示矩形
注:如果是利用v-for动态生成的结构,不能自动生成骨架屏,所以这里写出结构模板,给模板元素固定的宽高和背景颜色(与骨架屏相同),再用showSkeleton控制页面加载完成后隐藏结构模板。
1 <div v-show="showSkeleton"> 2 <navigator hover-class="none" class=""> 3 <div class=""> 4 <div class="skeleton-radius" style="background: #f5f6f7;"><img class="" mode="" /></div> 5 <div class=""> 6 <span class="skeleton-rect" style="background: #f5f6f7;height: 26px;"></span> 7 <div class="skeleton-rect" style="background: #f5f6f7;height: 15px;"></div> 8 </div> 9 </div> 10 <ul class="skeleton-rect" style="background: #f5f6f7;"></ul> 11 <ul class="skeleton-rect" style="background: #f5f6f7;"> 12 <li class=""><img class="" mode="" /></li> 13 <li class=""><img class="" mode="" /></li> 14 <li class=""><img class="" mode="" /></li> 15 </ul> 16 <div class="skeleton-rect" style="background: #f5f6f7;height: 20px;"></div> 17 </navigator> 18 </div>
此骨架屏为颜色渐浅的闪烁效果,如果想要修改的话只需在组件中修改css。
效果如下: