IntersectionOberver

2,213 阅读2分钟

Intersection Observer API提供了一种异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法


这个api可以干什么?

当你需要知道可视区内容时候,它可以帮你做监听,相比滚动计算方法更加当高效。

应用场景

  • 当页面滚动时,懒加载图片或其他内容。
  • 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
  • 为计算广告收益,检测其广告元素的曝光情况。
  • 根据用户是否已滚动到相应区域来灵活开始执行任务或动画。

下面会介绍:

  • web端IntersectionObserver的用法
  • 小程序端wx.createrInertersectionOberver
  • Taro框架中统一api

web端使用方法

MDN:Intersection Observer API

// 1. 创建observer对象
var options = {
    root: document.querySelector('#scrollArea'), 
    rootMargin: '0px', 
    threshold: 1.0, // number[] | number 阀值( 范围在0.0-1.0之间 )
}

var observer = new IntersectionObserver(callback, options);

一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值

阈值为1.0意味着目标元素完全出现root选项指定的元素中可见时,回调函数将会被执行

// 2. 观察目标
var target = document.querySelector('#listItem');
observer.observe(target);
var callback = function(entries, observer) { 
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

回调接收IntersectionObserverEntry对象和观察对象列表

小程序端使用方法

微信小程序API:wx.createIntersectionObserver

此api属于wxml中的方法,说明观察者是在视图线程中进行观察,回调函数在逻辑层触发

IntersectionObserver wx.createIntersectionObserver(Object component, Object options)

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

Page({
  onLoad: function(){
    wx.createIntersectionObserver({ thresholds: [1] })
      .relativeToViewport({bottom: 100})
      .observe('.target-class', (res) => {
        res.intersectionRatio // 相交区域占目标节点的布局区域的比例
        res.intersectionRect // 相交区域
        res.intersectionRect.left // 相交区域的左边界坐标
        res.intersectionRect.top // 相交区域的上边界坐标
        res.intersectionRect.width // 相交区域的宽度
        res.intersectionRect.height // 相交区域的高度
    })
  }
})

通过res.intersectionRatio > 0来判断观察目标是否出现在视图中,thresholds阀值要指定,以减少回调函数的触发,提高性能。

统一封装

在Taro项目中维护着H5项目和小程序项目,如果使用2个api,会造成逻辑冗余,不便于维护。所以提供了统一的api来使用

class AVisibleObserver {
  constructor(targetDomId, rootDomId, component, onActiveChange) {
    this.targetDomId = targetDomId
    this.rootDomId = rootDomId
    this.component = component
    this.onActiveChange = onActiveChange
  }
  observe() { }
  unobserve() { }
}


/**
 * thresholds,触发阈值的集合数组,默认 [0],例:我配置了 [0, 0.5, 0.8],那么当监听对象和参照物相交比例达到 0 / 0.5 / 0.8 时,会触发监听器的回调函数
 */
class WeappIntersectionObserver extends AVisibleObserver {
  constructor(targetDomId, rootDomId, component, onActiveChange) {
    super(targetDomId, rootDomId, component, onActiveChange)
    this.intersectionObserver = component.$scope
      .createIntersectionObserver({ initialRatio: 0, thresholds: [0,1] })
      .relativeToViewport({ top: 0, bottom: 0 })
  }
  observe() {
    this.intersectionObserver.observe(this.targetDomId, entry => {
      if( entry.intersectionRatio > 0 ){
        this.onActiveChange(true)
      }else {
        this.onActiveChange(false)
      }
    })
  }
  unobserve() {
    this.intersectionObserver.disconnect()
  }
}

class WebIntersectionObserver extends AVisibleObserver {
  constructor(targetDomId, rootDomId, component, onActiveChange) {
    super(targetDomId, rootDomId, component, onActiveChange)
    this.intersectionObserver = new IntersectionObserver(entries => {
      if( entries[0].intersectionRatio > 0 ){
        onActiveChange(true)
      }else{
        onActiveChange(false)
      }
    }, {
      root: document.querySelector(rootDomId)
    })
  }
  observe() {
    if( document.querySelector(this.targetDomId) ){
      this.intersectionObserver.observe(document.querySelector(this.targetDomId))
    }
  }
  unobserve() {
    this.intersectionObserver.disconnect()
  }
}

class VisibleObserver extends AVisibleObserver {
  constructor(targetDomId, rootDomId, component, onActiveChange) {
    super(targetDomId, rootDomId, component, onActiveChange)
    if (process.env.TARO_ENV === 'h5') {
      this.actualVisibleObserve = new WebIntersectionObserver(targetDomId, rootDomId, component, onActiveChange)
    } else {
      this.actualVisibleObserve = new WeappIntersectionObserver(targetDomId, rootDomId, component, onActiveChange)
    }
  }
  observe() {
    this.actualVisibleObserve.observe()
  }
  unobserve() {
    this.actualVisibleObserve.unobserve()
  }
}


function createVisibleObserver({ targetDomId, rootDomId, component }, onActiveChange){
  const visibleObserver =  new VisibleObserver(targetDomId, rootDomId, component, onActiveChange)
  visibleObserver.observe()
  return visibleObserver
}

export default createVisibleObserver
this._observer = createVisibleObserver({
  targetDomId, 
  rootDomId: '#app',
  component: this
}, onActiveChange)

polyfill