骨架屏

Friday, March 11, 2022

背景

小程序首页进入时,需要等待后端数据返回进行渲染,在等待过程中页面可能会是空屏状态,为减少用户等待的感知,加入骨架屏进行体验优化。

原理

通过类名标记需要“骨架”化的节点,再通过skeleton组件获取这些节点的位置大小信息,渲染出位置大小与真实节点一致的骨架节点,利用定位与层级对原页面覆盖。在请求过程中展示skeleton组件,请求完成后则隐藏,从而达到预期效果。

实现

首先我们在页面中引入骨架屏的skeleton组件

因为我们最终要获取各骨架节点的信息,所以需要对他们提前使用类名标记

1. `skeleton` 页面根节点
2. `skeleton-rect` 需要绘成方形骨架的节点
3. `skeleton-arc ` 需要绘成圆形骨架的节点
// index.wxml
<skeleton isShow="{{ loading }}" bgColor="#ccc"></skeleton>
<view class="skeleton container">
  <image catchtap="onClick" class="avatar skeleton-arc" src="..."></image>
  <user-name></user-name>
  <view class="block skeleton-rect"></view>
  <view class="block skeleton-rect"></view>
  <view class="block skeleton-rect"></view>
  <view class="block skeleton-rect"></view>
  <view class="block skeleton-rect"></view>
  <view class="block skeleton-rect"></view>
</view>

骨架屏skeleton组件的实现

  1. 将组件设为绝对定位(fixed),高层级(z-index),宽高设为父级的100%

  2. 页面节点挂载完成(ready())时,用节点查询方法(wx.createSelectorQuery().selectAll()),找到所有相关类名的元素(skeleton-rectskeleton-arc

  3. 找到所有元素后,根据方圆分别用两个数组存放位置大小信息,最终使用wx:for完成渲染

// skeleton.js
Component({
  properties: {
    isShow: { // 是否展示
      type: Boolean,
      value: true
    },
    bgColor: { // 骨架屏背景
      type: String,
      value: '#fff'
    },
    selects: { // 页面根元素类名
      type: String,
      value: 'skeleton'
    }
  },
  data: {
    skeletonRect: [], // 方形列表
    skeletonArc: [] // 圆形列表
  },
  ready() {
    this.fillRect()
    this.fillCircle()
  },
  methods: {
    // 绘制方形
    fillRect() {
      wx.createSelectorQuery()
        .selectAll(`.${this.data.selects} >>> .${this.data.selects}-rect`)
        .boundingClientRect((rect) => {
          this.setData({
            skeletonRect: rect
          })
        })
        .exec()
    },
    // 绘制圆形
    fillCircle() {
      wx.createSelectorQuery()
        .selectAll(`.${this.data.selects} >>> .${this.data.selects}-arc`)
        .boundingClientRect((rect) => {
          this.setData({
            skeletonArc: rect
          })
        })
        .exec()
    },
  }
})
// skeleton.wxml
<view style="background: {{bgColor}};" class="skeleton-wrap" wx:if="{{isShow}}">
  <!--画圆-->
  <view class="skeleton-item skeleton-ani" wx:for="{{skeletonRect}}" wx:key="id"
        style="width: {{item.width}}px; height: {{item.height}}px; top: {{item.top}}px; left: {{item.left}}px;">
  </view>
  <!--画方-->
  <view class="skeleton-item skeleton-ani" wx:for="{{skeletonArc}}"
        wx:key="id" style="width: {{item.width}}px; height: {{item.height}}px; top: {{item.top}}px; left: {{item.left}}px; border-radius: {{item.width}}px;">
  </view>
</view>
// skeleton.wxss
.skeleton-wrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9998;
  overflow: hidden;
}
.skeleton-item{
  position: absolute;
  background-color: #eee;
}
/*动画*/
.skeleton-ani {
  background: linear-gradient(
          110deg,
          transparent 40%,
          rgba(255, 255, 255, .5) 50%,
          transparent 60%) #eee;
  background-size: 200%;
  background-position-x: 180%;
  animation: ani 1.5s linear infinite;
}

@keyframes ani {
  to {
    background-position-x: -20%;
  }
}

获取页面相关节点, 使用跨组件的后代选择器>>>,可以拿到其它自定义内的skeleton-rect/acr节点

代码片段