一个非常简单、实用、改得动的微信小程序骨架屏组件 | 七日打卡

1,935 阅读4分钟

前言

骨架屏是一种能良好提高用户体验的常见手段,不至于刚进应用就看到页面排版混乱的恶心体验,不过之前大多都是在H5的应用中,本章就聊聊在微信小程序中如何使用骨架屏,以及探讨下骨架屏的实现原理。

在了解下面的内容前,有件很重要的事情:

  • 小程序不存在预渲染的概念!!!
  • 小程序不存在预渲染的概念!!!
  • 小程序不存在预渲染的概念!!!

重要的事情说三遍,所以我们不存在说先提前渲染好页面返回给用户的情况。

但是呢!但是呢!我们还是可以通过骨架屏组件的使用,来很好的等待一些异步数据更新渲染的,下面就来开始聊聊吧。

使用过程

从上面的效果图我们能看到大致的效果了,我们先聊聊大致的使用过程,分三步走。

  • 引入组件

我们把骨架屏做成一个公共组件,把它引入到想要加的页面中。

// index.json
{
  "usingComponents": {
    "skeleton": "./../../components/skeleton/skeleton"
  }
}
  • 添加组件结构,给页面根节点 class 添加skeleton类名,给需要画成方形的元素添加skeleton-rect类名,给需要画成圆形的元素添加skeleton-arc类名。
// index.wxml 
<!-- 骨架屏 -->
<skeleton isShow="{{ loading }}"></skeleton>
<view class="skeleton container">
  <image class="avatar skeleton-arc" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/6/16f786fbd44167e9~tplv-t2oaga2asx-image.image"></image>
  <view class="username 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 class="block skeleton-rect"></view>
</view>
  • 特定时机销毁组件
// index.js
const app = getApp()
Page({
  data: {
    loading: true
  },
  onLoad() {
    // 模拟异步请求5秒后销毁组件
    setTimeout(() => {
      this.setData({
        loading: false
      })
    }, 5000)
  }
})

在小程序中使用骨架屏还是比较简单的 easy~ 是吧,主要就是注意什么时机来把控骨架屏的显示与隐藏就完事。

实现原理

接下来我们就来分析下实现这个骨架屏组件的原理吧,(先打个针)其实也是非常简单的 easy~ 啦。

  • 首先把组件设置为绝对定位(fixed),设定一个比较高的层级(z-index),大小为整个屏幕的大小也就是100%。
  • 在页面节点挂载完成(ready())的时候,用节点查询方法(wx.createSelectorQuery().selectAll()),找到所有相关类名的元素,也就是我们前面说过的添加在skeleton类名下的有skeleton-rect类名与skeleton-arc类名的元素了。
  • 找到所有元素后,将它们的位置与大小信息储存起来,我们把元素划分为方形与圆形两个两个数组储存,最后用wx:for给渲染出来就大功告成了。

大致就是怎么个过程了,根据这个分析相信你也差不多能写个大概了,下面就来看看小编的实现过程吧。

过程一

先把结构与样式怼您出来看看吧,没啥难度^-^。

// skeleton.wxml
<view style="background: {{bgColor}};" class="skeleton-wrap" catch:touchmove="_none" 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>

样式也没多少,主要可能需要注意的就是添加了一个小动画效果,它利用CSS3的背景渐变属性linear-gradient与动画animation来实现,对于animation就不多说了,比较常见了,前端多多少少是知道了解的,不知道的拉出去打吧-.-。对于linear-gradient可能就见的比较少了,不懂的点它点它。点我

// 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%;
  }
}

过程二

我们骨架屏已经是一个fixed布局且层级较高的组件,基本是完整遮挡住了页面,下面开始映射页面元素绘制出对应的方形与原型元素,我将所要绘制的元素都统一设置成absolute,再通过wx.createSelectorQuery().selectAll()API拿到相关元素的widthheighttopleft,用来绘制元素就行啦。

// 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 () {
      // 获取页面相关节点, 使用跨组件的后代选择器(>>>), 注意if和hidden的拿不到
      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-rect类名与skeleton-arc类名来进入自己另外定义的组件里面,但只要它在页面根节点上有skeleton类名的下面,就一样能获取到该元素的信息。

附上wx.createSelectorQuery().selectAll()API获取的属性:

源码

以上就是本文的全部内容,是不是非常简单,没有骗你吧?即引即用,是不是也很实用呢?以上代码不要告诉我你看不懂(拉出去打...)。

最后送上源码 传送门,拜拜,下期见。