骨架屏也是优化的一个重要环节,如若遇到网络不佳时,接口数据返回的比较慢,然后再渲染页面,页面会有空白的情况,那么骨架凭无疑是一个不错的解决方案. 看了插件市场的案例,进行的修改调整.

方案

市场上有这么几种解决方案

  1. - UI给定每个页面的骨架屏图片
  2. - 缺点:ui那里比较耗时间
  3. - 前端根据页面进行页面股价进行css绘制
  4. - 前端这里比较耗时间
  5. - 根据dom节点进行绘制
  6. - 一次组件封装,可重复调用

思路

等待页面HTML加载完成,获取页面的dom节点信息,进行绘制展示骨架屏组件,接口数据请求完成,隐藏骨架屏,渲染页面.
那么dom节点如何获取?
在每个节点上定义个类名,获取该元素节点信息即可。
Uniapp 骨架屏 - 图1

skeleton组件

  1. <template>
  2. <view
  3. v-if="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: skebgcolor,
  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: skebgcolor,
  36. borderRadius: item.width + 'px',
  37. position: 'absolute',
  38. left: item.left + 'px',
  39. top: item.top + 'px'
  40. }"
  41. ></view>
  42. <view class="spinbox" v-if="loading == 'spin'"><view class="spin"></view></view>
  43. </view>
  44. </template>
  45. <script>
  46. export default {
  47. name: 'skeleton',
  48. props: {
  49. //骨架屏背景色
  50. bgcolor: {
  51. type: String,
  52. value: '#FFF'
  53. },
  54. //绘制形状
  55. selector: {
  56. type: String,
  57. value: 'skeleton'
  58. },
  59. //是否显示loading
  60. loading: {
  61. type: String,
  62. value: 'spin'
  63. },
  64. //是否显示骨架屏组件
  65. show: {
  66. type: Boolean,
  67. value: false
  68. },
  69. //发生变化就开始获取don节点信息
  70. isNodes: {
  71. type: Number,
  72. value: false
  73. },
  74. //绘制形状背景
  75. skebgcolor: {
  76. type: String,
  77. default: 'rgb(194, 207, 214)'
  78. }
  79. },
  80. data() {
  81. return {
  82. loadingAni: ['spin', 'chiaroscuro'],
  83. systemInfo: {},
  84. skeletonRectLists: [],
  85. skeletonCircleLists: []
  86. };
  87. },
  88. watch: {
  89. isNodes(val) {
  90. this.readyAction();
  91. }
  92. },
  93. mounted() {
  94. this.attachedAction();
  95. },
  96. methods: {
  97. attachedAction () {
  98. //默认的首屏宽高,防止内容闪现
  99. const systemInfo = uni.getSystemInfoSync();
  100. this.systemInfo = {
  101. width: systemInfo.windowWidth,
  102. height: systemInfo.windowHeight
  103. };
  104. this.loading = this.loadingAni.includes(this.loading) ? this.loading : 'spin';
  105. },
  106. readyAction () {
  107. console.log('子组件readyAction');
  108. const that = this;
  109. //绘制背景
  110. uni.createSelectorQuery()
  111. .selectAll(`.${this.selector}`)
  112. .boundingClientRect()
  113. .exec(function(res) {
  114. that.systemInfo.height = res[0][0].height + res[0][0].top;
  115. });
  116. //绘制矩形
  117. this.rectHandle();
  118. //绘制圆形
  119. this.radiusHandle();
  120. },
  121. rectHandle () {
  122. const that = this;
  123. //绘制不带样式的节点
  124. uni.createSelectorQuery()
  125. .selectAll(`.${this.selector}-rect`)
  126. .boundingClientRect()
  127. .exec(function(res) {
  128. that.skeletonRectLists = res[0];
  129. });
  130. },
  131. radiusHandle() {
  132. const that = this;
  133. uni.createSelectorQuery()
  134. .selectAll(`.${this.selector}-radius`)
  135. .boundingClientRect()
  136. .exec(function(res) {
  137. that.skeletonCircleLists = res[0];
  138. });
  139. }
  140. }
  141. };
  142. </script>
  143. <style>
  144. .spinbox {
  145. position: fixed;
  146. display: flex;
  147. justify-content: center;
  148. align-items: center;
  149. height: 100%;
  150. width: 100%;
  151. z-index: 9999;
  152. }
  153. .spin {
  154. display: inline-block;
  155. width: 64rpx;
  156. height: 64rpx;
  157. }
  158. .spin:after {
  159. content: ' ';
  160. display: block;
  161. width: 46rpx;
  162. height: 46rpx;
  163. margin: 1rpx;
  164. border-radius: 50%;
  165. border: 5rpx solid #409eff;
  166. border-color: #409eff transparent #409eff transparent;
  167. animation: spin 1.2s linear infinite;
  168. }
  169. @keyframes spin {
  170. 0% {
  171. transform: rotate(0deg);
  172. }
  173. 100% {
  174. transform: rotate(360deg);
  175. }
  176. }
  177. .chiaroscuro {
  178. width: 100%;
  179. height: 100%;
  180. background: rgb(194, 207, 214);
  181. animation-duration: 2s;
  182. animation-name: blink;
  183. animation-iteration-count: infinite;
  184. }
  185. @keyframes blink {
  186. 0% {
  187. opacity: 0.4;
  188. }
  189. 50% {
  190. opacity: 1;
  191. }
  192. 100% {
  193. opacity: 0.4;
  194. }
  195. }
  196. @keyframes flush {
  197. 0% {
  198. left: -100%;
  199. }
  200. 50% {
  201. left: 0;
  202. }
  203. 100% {
  204. left: 100%;
  205. }
  206. }
  207. .shine {
  208. animation: flush 2s linear infinite;
  209. position: absolute;
  210. top: 0;
  211. bottom: 0;
  212. width: 100%;
  213. background: linear-gradient(to left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.85) 50%, rgba(255, 255, 255, 0) 100%);
  214. }
  215. </style>

demo

wecom-temp-9b0f27679b1f37b19c7676aff3bf686f.jpg wecom-temp-4836d4f3d30a84674c6606acf8615265.jpg

  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="__desc skeleton-rect">123</view>
  18. <view class="__desc skeleton-rect">123</view>
  19. <view class="__desc skeleton-rect">123</view>
  20. </view>
  21. <!--引用组件-->
  22. <skeleton :show="showSkeleton" :isNodes="isNodes" ref="skeleton" loading="chiaroscuro" selector="skeleton" bgcolor="#FFF" :skebgcolor="'rgb(194, 207, 214)'"></skeleton>
  23. </view>
  24. </template>
  25. <script>
  26. import skeleton from '@/components/skeleton.vue';
  27. export default {
  28. data() {
  29. return {
  30. userInfo: {
  31. avatarUrl: 'http://pic.wxhand.com/dev/student_image/d4305c73c610aa4f0841243c7455c76f',
  32. nickName: 'along'
  33. },
  34. lists: ['第1行数据', '第2行数据', '第3行数据', '第4行数据', '第5行数据', '第6行数据'], //如果没有默认数据
  35. showSkeleton: true, //骨架屏显示隐藏
  36. isNodes: 0 //控制什么时候开始抓取元素节点,只要数值改变就重新抓取
  37. };
  38. },
  39. components: {
  40. skeleton
  41. },
  42. onLoad () {
  43. this.$nextTick(() => {
  44. this.isNodes++;
  45. })
  46. setTimeout(() => {
  47. this.showSkeleton = false;
  48. }, 10000);
  49. }
  50. };
  51. </script>
  52. <style>
  53. .container {
  54. padding: 20rpx 60rpx;
  55. }
  56. .userinfo {
  57. display: flex;
  58. flex-direction: column;
  59. align-items: center;
  60. margin-top: 40rpx;
  61. }
  62. .userinfo-avatar {
  63. width: 128rpx;
  64. height: 128rpx;
  65. margin: 20rpx;
  66. border-radius: 50%;
  67. }
  68. .userinfo-nickname {
  69. color: #aaa;
  70. }
  71. .usermotto {
  72. margin-top: 200px;
  73. }
  74. .lists {
  75. margin: 10px 0;
  76. }
  77. .list {
  78. margin-right: 10px;
  79. }
  80. .__desc {
  81. font-size: 24rpx;
  82. margin-top: 10rpx;
  83. }
  84. </style>