IntersectionObserver API 应用总结

5,280 阅读3分钟

前言

为了提升页面性能,项目中经常需要等待一些元素进入可视区域后再执行一些相关操作,比如图片懒加载或者加载更多等场景。我们一般都是监听根元素的 scroll 事件,然后结合元素的位置信息来判断,这种方法计算量大,而且 scroll 事件触发频率高很可能造成性能问题,以及代码阅读性不高。 IntersectionObserver 接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗 (viewport) 交叉状态的方法,IntersectionObserver 是异步的,不随着目标元素的滚动同步触发,性能较好;简洁的 API 清晰易读,逼格也很高。

今天主要给大家分享下本人在日常开发中关于 IntersectionObserver 的一些实际应用,关于 API 原理以及介绍大家可以阅读 Intersection Observer API - MDNIntersectionObserver API 使用教程

应用

图片懒加载

使用 IntersectionObserver 非常容易实现图片懒加载,首先需要观察懒加载元素,然后等元素进入可视区域后设置图片 src;同时,还可以结合 IntersectionObserver.rootMargin 实现提前加载图片,一般可以设置为 1~2 倍浏览器窗口的视口高度,优化用户体验。

/**
 * @method lazyLoad
 * @param {NodeList} $imgList      图片元素集合
 * @param {number}   preloadHeight 预加载高度
 */
export function lazyLoad($imgList, preloadHeight = 1000) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) { // 目标元素出现在 root 可视区,返回 true
        const $target = entry.target
        const src = $target.getAttribute('lazyload')

        if (src) {
          $target.setAttribute('src', src) // 真正加载图片
        }
        observer.unobserve($target) // 解除观察
      }
    })
  }, {
    rootMargin: `0px 0px ${preloadHeight}px 0px`,
  })

  Array.prototype.forEach.call($imgList, ($item) => {
    if ($item.getAttribute('src')) return // 过滤已经加载过的图片
    observer.observe($item) // 开始观察
  })
}

使用方法:

  • 图片元素设置 lazyload 属性
<img lazyload="图片链接" alt="图片说明">
  • 观察图片元素
lazyLoad(document.querySelectorAll("[lazyload]"))

元素吸顶、吸底

在一些 APP 外的分享页中经常会看到头部或者底部会固定一个 banner 位,一开始 banner 可能处于正常位置,当即将离开可视区域的时候会固定在屏幕视口顶部或者底部,这种场景页非常适合用 IntersectionObserver 来处理。如果页面结构比较简单可以直接使用 css 粘性布局
IntersectionObserver 实现元素固定思路也很简单,首先需要给固定元素包一层父元素,父元素指定高度占位,防止固定元素吸附时页面抖动,然后观察父元素的可视状态变化,当父元素即将离开可视区域时改变固定元素的样式。

/**
 * @method fixBanner
 * @param {HTMLElement} $observeEle 观察元素
 * @param {HTMLElement} $fixEle     固定定位元素
 */
export function fixBanner($observeEle, $fixEle) {
  const $ele = $fixEle
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        $ele.style.cssText = ''
      } else {
        $ele.style.cssText = 'position: fixed; top: 0; left: 0'
      }
    })
  }, {
    threshold: 1, // threshold 设置为 1 表示目标元素完全可见时触发回调函数
  })

  observer.observe($observeEle) // 开始观察
}

使用方法:

  • 元素结构
<!-- 父元素用来占位和观察可视状态 -->
<div class="mod_banner">
  <div class="banner">吸顶元素</div>
</div>
  • 观察元素
fixBanner(
  document.querySelector('.mod_banner'),
  document.querySelector('.banner')
)

加载更多

IntersectionObserver 实现加载更多需要在列表后面增加一个尾部元素(比如加载更多动画),当尾部元素进入可视区域就加载更多数据,注意尾部元素一定要一直处于所有列表元素的后面。

function loadMore() {
  const observer = new IntersectionObserver(
    (entries) => {
      const loadingEntry = entries[0]

      if (loadingEntry.isIntersecting) {
        // 请求数据并插入列表
      }
    },
    {
      rootMargin: '0px 0px 600px 0px', // 提前加载高度
    },
  )

  observer.observe(document.querySelector('.mod_loading')) // 观察尾部元素
}
提前加载高度不能随意设置,如果设置太大会导致尾部元素一直处于可视状态。

完整代码

曝光上报

业务开发完成后,产品同学一般都需要我们去上报一些数据,项目上线后可以通过数据分析来进行产品迭代优化。基本上报事件会包括页面曝光、元素曝光和元素点击,页面曝光可以结合页面的生命周期上报,元素点击可以使用事件冒泡处理,而元素曝光检测非常麻烦。因为元素的层级关系复杂,影响元素显隐状态的样式属性也很多,导致我们往往会耗费很大的精力在元素的曝光计算上,而且还可能会对页面造成严重的性能问题。数据上报作为一个和页面展示无关的附加功能,不应该对页面的性能造成影响,所以这里非常适合用 IntersectionObserver 来完成。

兼容性

这么好用的 API,我们当然要看下它的兼容性,不幸的是还有很多的浏览器不支持,不过官方提供了 polyfill,这样我们就可以在项目中愉快地使用了。

  • 安装 ployfill
npm install intersection-observer --save-dev
  • 入口文件引入
import 'intersection-observer'

❤️ 总结

这篇文章主要给大家分享了 IntersectionObserver API 的一些应用,关于更多的使用场景等着我们去发掘。
感谢您的阅读,文章中有什么不对的地方欢迎指出~

参考文章