Intersection Observer API提供了异步观察目标元素与祖先元素或顶级文档viewport交集变化的方法
Intersection Observer API 会注册一个回调方法,每当期望被监视的元素进入或者退出另外一个元素的时候(或者浏览器的视口)该回调方法将会被执行

  1. // 用法
  2. var options = {
  3. root: document.querySelector("#scrollArea"),
  4. // 该属性值是用作root元素和target发生交集时候的计算交集的区域范围
  5. rootMargin: "0px",
  6. // 交叉比
  7. // 目标(target)元素与根(root)元素之间的交叉度是交叉比(intersection ratio)
  8. // 这是目标(target)元素相对于根(root)的交集百分比的表示,它的取值在0.0和1.0之间
  9. threshold: 1.0,
  10. };
  11. // 实例化observer对象
  12. var observer = new IntersectionObserver(callback, options);
  13. // 为每个观察者配置一个目标
  14. var target = document.querySelector("#listItem");
  15. observer.observe(target);
  16. // 只要目标满足指定的阈值,就会调用回调
  17. // 回调接收IntersectionObserverEntry对象和观察者对象的列表
  18. var callback = function (entries, observer) {
  19. entries.forEach((entry) => {
  20. // Each entry describes an intersection change for one observed
  21. // target element:
  22. // entry.boundingClientRect
  23. // entry.intersectionRatio
  24. // entry.intersectionRect
  25. // entry.isIntersecting
  26. // entry.rootBounds
  27. // entry.target
  28. // entry.time
  29. });
  30. };

React用法

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.targets = [];
    this.observer = null;
  }

  componentDidMount() {
    const root = document.querySelector("#scrollArea");
    const options = {
      root,
      rootMargin: "40px",
      threshold: 1.0,
    };
    // 创建一个 IntersectionObserver对象,挂载到上下文,注册callback;
    this.observer = new IntersectionObserver(this.observerCallback, options);
    // 挂载上下文不会触发组件的re-render,这里利用element.forceUpdate强制组件刷新,目的是在ref回调里注册观察者
    this.forceUpdate();
  }

  observerCallback = (entries) => {
    // 迭代观察者列表通过对比ref应用是否相同,来更新intersection(是否交集)值
    this.targets.forEach((target) => {
      for (const entry of entries) {
        对比引用
        if (target.ref === entry.target) {
          target.intersecting = entry.isIntersecting;
          break;
        }
      }
    });
    this.forceUpdate();
  };

  // 根据观察者对象决定如何渲染组件
  isRenderCell = (target) => {
    return target ? target.intersecting : false;
  };

  getTarget = (index) => {
    const nameIndex = index;
    return this.targets.find((t) => t.index === nameIndex);
  };

  // ref回调注册观察者对象
  getNameCellRef = (ref, index, target) => {
    let refHeight = lodash.get(ref, "parentNode", {});
    // 通过dom api getBoundingClientRect获取列表子元素的属性
    refHeight =
      refHeight.getBoundingClientRect &&
      refHeight.getBoundingClientRect().height;
    // 因为回调每次re-render都会执行,所以这里通过条件执行注册
    if (!target && refHeight && this.observer) {
      this.observer.observe(ref);
      this.targets.push({ ref, intersecting: true, index, height: refHeight });
    }
  };

  render() {
    console.log(this.targets, "this.targets");
    return (
      <div id="scrollArea">
        <Table
          primaryKey="name"
          dataSource={data}
          maxBodyHeight={600}
          fixedHeader
        >
          <Table.Column
            title="Name"
            dataIndex="name"
            cell={(name, key) => {
              // 通过索引值获取target对象
              const target = this.getTarget(key);
              return (
                <div ref={(ref) => this.getNameCellRef(ref, key, target)}>
                  {this.isRenderCell(target) ? (
                    name
                  ) : (
                    <div style={{ height: target ? target.height : 0 }}>
                      {key}
                    </div>
                  )}
                </div>
              );
            }}
          />
        </Table>
      </div>
    );
  }
}