带你彻底了解微信小程序的createIntersectionObserver,实现长列表性能优化、图片懒加载、吸顶等等操作

8,019 阅读4分钟

大家好,我是百慕大,废话不多话直接进入主题。

wx.createIntersectionObserver 的使用语法

创建并返回一个 IntersectionObserver 对象实例。在自定义组件或包含自定义组件的页面中,应使用  this.createIntersectionObserver([options])  来代替。

wx.createIntersectionObserver(Object component, Object options)

Object Options(选项)

属性类型默认值必填说明
thresholdsArray[0]一个数值数组,包含所有阈值。可以是一组0.0到1.0之间的数组
initialRationumber0初始的相交比例,如果调用时检测到的相交比例与这个值不相等且达到阈值,则会触发一次监听器的回调函数。
observeAllbooleanfalse是否同时观测多个目标节点(而非一个),如果设为 true ,observe 的 targetSelector 将选中多个节点(注意:同时选中过多节点将影响渲染性能)

IntersectionObserver 对象

IntersectionObserver 对象用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。这是什么意思呢?就是说可以判断观察目标是否有出现在指定区域内。

上面是微信开放文档中的说法,通俗的理解就是 IntersectionObserver 对象用来创建窗口用来观察目标元素。

该对象有以下方法:

IntersectionObserver.relativeTo(string selector, Object margins)

使用选择器指定一个节点,作为参照区域之一。也就是说 relativeTo(string selector) 会创建一个区域(视口)用来观察目标元素。

IntersectionObserver.relativeTo(string selector, Object margins)

// Object margins 的具体属性
// left:节点布局区域的左边界
// right:节点布局区域的右边界
// top:节点布局区域的上边界
// bottom:节点布局区域的下边界

IntersectionObserver.relativeToViewport(Object margins)

指定页面显示区域作为参照区域之一。

IntersectionObserver.relativeToViewport(Object margins)

// Object margins 的具体属性
// left:节点布局区域的左边界
// right:节点布局区域的右边界
// top:节点布局区域的上边界
// bottom:节点布局区域的下边界

relativeToViewport 的功能与 relativeTo 方法是相同的。

这两个方法的差异是:

  • relativeTo 方法是使用选择器指定参照区域
  • relativeToViewport 则是调用页面的显示区域为参照区域

下文中有图片可以很好的展示参照区域

IntersectionObserver.observe(string targetSelector, function callback)

上面的 IntersectionObserver.relativeToViewportIntersectionObserver.observe 方法都会将 IntersectionObserver 对象返回。

返回的 IntersectionObserver 对象有 observe 方法,以下:

IntersectionObserver.observe(string targetSelector, function callback)

指定目标节点,只有目标节点满足指定的阈值才会调用回调。

这与 wx.createIntersectionObserverdthresholds 参数是密切相关的。

我们可以通过下图可以清晰的了解观察区域和观察目标的关系:

Untitled-2021-12-06-2335.png

举例说明

在这里就用微信官方文档提供的例子来讲解各个参数的作用

官方例子

onLoad() {
  this._observer = wx.createIntersectionObserver(this)
  this._observer
    .relativeTo('.scroll-view')
    .observe('.ball', (res) => {
      console.log(res);
      this.setData({
        appear: res.intersectionRatio > 0
      })
    })
},
onUnload() {
  if (this._observer) this._observer.disconnect()
}

我们可以看到代码中有.relativeTo('.scroll-view')代码,那么观察区域就如下图绿圈所示:

image.png

如果使用了.relativeTo('.scroll-view', {top: 100, bottom: 100}),那么观察区域就为下图绿圈所示:

image.png

threshold 参数

这里讲一下 threshold 参数,threshold 是目标节点观察区域边界盒交叉区域的比例值,只有当目标节点满足阈值的时候 observe 的回调才会执行。

threshold 的默认值是 [0],也就是说只有目标节点出现或消失在观察区域中时才会执行回调函数。

如果设置 threshold 为 [0, 0.25, 0.5, 0.75. 1] 时,那么目标节点每次超过 25%时就会执行回调。

observeAll

是否观察多个节点。如果是将会同时观察多个目标。一般用于观察列表中的多个元素。

官方文档地址

长列表优化

可以参考这篇文章:

解决小程序渲染复杂长列表,内存不足问题

图片懒加载

<image id="image-{{id}}" class="image" mode="widthFix" src="{{realSrc}}">
</image>
// components/lazyImage/lazyImage.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    // 传入的图片的src
    src: {
      type: String,
      value: '',
    },
  },

  /**
   * 组件的初始数据
   */
  data: {
    id: '',
    realSrc: '',
  },
  ready() {
    this.setData({
      id: this.randomString(10),
    })
    this._observe = wx.createIntersectionObserver(this)
    this._observe
      .relativeToViewport()
      .observe(`#image-${this.data.id}`, (res) => {
        let { intersectionRatio } = res
        // 当显示区域出现image时就将src赋值给image
        if (intersectionRatio > 0 && this.data.realSrc == '') {
          this.setData({
            realSrc: this.data.src,
          })
        }
      })
  },

  /**
   * 组件的方法列表
   */
  methods: {
    /**
     * 生成随机的字符串
     */
    randomString(len) {
      len = len || 32
      var $chars =
        'abcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
      var maxPos = $chars.length
      var pwd = ''
      for (var i = 0; i < len; i++) {
        pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
      }
      return pwd
    },
  },
})

在微信小程序开发工具中预览

吸顶

<view>
  <view class="box1"></view>
  <view class="box2">
    <view class="nav {{isFixed?'fixed':''}}"></view>
  </view>
  <view class="box3"></view>
</view>
Page({
  data: {
    isFixed: false,
  },
  onLoad() {},
  onReady() {
    let { windowHeight } = wx.getSystemInfoSync()
    this._obeserve = wx.createIntersectionObserver(this, {
      initialRatio: 0,
    })
    this._obeserve
      .relativeToViewport({
        top: 50,
        bottom: -windowHeight,
      })
      .observe('.box2', (res) => {
        console.log('res', res)
        let { intersectionRatio, boundingClientRect } = res
        if (intersectionRatio > 0 && boundingClientRect.top <= 0) {
          this.setData({
            isFixed: true,
          })
        } else if (intersectionRatio <= 0 && boundingClientRect.top > 0) {
          this.setData({
            isFixed: false,
          })
        }
      })
  },
})
.box1 {
  display: block;
  height: 200px;
  background: red;
}

.box2 {
  display: block;
  height: 50px;
}

.nav {
  display: block;
  height: 50px;
  background: blue;
}

.fixed {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
}

.box3 {
  display: block;
  height: 3000rpx;
}

在微信小程序开发工具上预览

哦哦哦,上面这些代码都忘记执行IntersectionObserver.disconnect了,记得在页面卸载或组件实例被从页面节点树移除时执行IntersectionObserver.disconnect取消监听。

结语

其他好文

开源库推荐」uniapp(vue3 版本) 中如何优雅的解决 app.onLaunch 与页面 onLoad 异步问题

原创不易,对你有帮助的话可以点个赞吗(呜呜呜)