当前位置: 首页 > article >正文

使用Intersection Observer API 检测元素是否出现在可视窗口

使用Intersection Observer API 检测元素是否出现在可视窗口

API解读:

Intersection Observer API提供了一种异步检测目标元素与祖先元素或视口(可统称为根元素)相交情况变化的方法。

注意点:因为该 API 是异步的,它不会随着目标元素的滚动同步触发,而IntersectionObserver API是通过requestIdleCallback()实现,即只有浏览器空闲下来,才会执行观察器。

Intersection observer 的重要概念

Intersection observer API 有以下五个重要的概念:

  • 目标(target)元素 — 我们要监听的元素
  • 根(root)元素 — 帮助我们判断目标元素是否符合条件的元素
  • 以下两种情况根元素会默认为顶级文档的视口(一般为 html)。
    • 目标元素不是可滚动元素的后代且不传值时
    • 指定根元素为 null
  • 交叉比(intersection ratio)—目标元素与根根的交集相对于目标元素百分比的表示(取值范围 0.0-1.0)。
  • 阈值(threshold) — 回调函数触发的条件。
  • 回调函数(callback) — 为该 API 配置的函数,会在设定的条件下触发。

用法

是以new的形式声明一个对象,接收两个参数callbackoptions

const io = new IntersectionObserver(callback [,options])

io.observe(DOM)

callback

callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例。描述了目标元素与root的交叉状态。具体参数如下:

属性说明
boundingClientRect返回包含目标元素的边界信息,返回结果与element.getBoundingClientRect() 相同
intersectionRatio返回目标元素出现在可视区的比例
intersectionRect用来描述root和目标元素的相交区域
isIntersecting返回一个布尔值,下列两种操作均会触发callback:1. 如果目标元素出现在root可视区,返回true。2. 如果从root可视区消失,返回false
rootBounds用来描述交叉区域观察者(intersection observer)中的根.
target目标元素:与根出现相交区域改变的元素 (Element)
time返回一个记录从 IntersectionObserver 的时间原点到交叉被触发的时间的时间戳

表格中加粗的两个属性是比较常用的判断条件:isIntersecting(是否出现在可视区)和intersectionRatio(出现在可视区的比例)

options

options是一个对象,用来配置参数,也可以不填。共有三个属性,具体如下:

属性说明
root所监听对象的具体祖先元素。如果未传入值或值为null,则默认使用顶级文档的视窗(一般为html)。
rootMargin计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要。所有的偏移量均可用像素(px)或百分比(%)来表达, 默认值为"0px 0px 0px 0px"。
threshold一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会触发callback。默认值为0。

方法

介绍了这么多配置项及参数,差点忘了最重要的,IntersectionObserver有哪些方法? 如果要监听某些元素,则必须要对该元素执行一下observe

方法说明
observe()开始监听一个目标元素
unobserve()停止监听特定目标元素
takeRecords()返回所有观察目标的IntersectionObserverEntry对象数组
disconnect()使IntersectionObserver对象停止全部监听工作

实际应用

  • 图片懒加载
const imgList = [...document.querySelectorAll('img')]

var io = new IntersectionObserver((entries) =>{
  entries.forEach(item => {
    // isIntersecting是一个Boolean值,判断目标元素当前是否可见
    if (item.isIntersecting) {
      item.target.src = item.target.dataset.src
      // 图片加载后即停止监听该元素
      io.unobserve(item.target)
    }
  })
})
// observe遍历监听所有img节点
imgList.forEach(img => io.observe(img))
  • 埋点曝光

假如有个需求,对一个页面中的特定元素,只有在其完全显示在可视区内时进行埋点曝光。

const boxList = [...document.querySelectorAll('li')]

var io = new IntersectionObserver((entries) =>{
  entries.forEach(item => {
    // intersectionRatio === 1说明该元素完全暴露出来,符合业务需求
    if (item.intersectionRatio === 1) {
      // 。。。 埋点曝光代码
      // do something...
      io.unobserve(item.target)
    }
  })
}, {
  root: null,
  threshold: 1, // 阀值设为1,当只有比例达到1时才触发回调函数
})

// observe遍历监听所有box节点
boxList.forEach(item => io.observe(item))

  • demo:

大家可以在自己电脑运行一下下面的代码,会有更深的理解。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IntersectionObserver</title>
    <style>
      li {
        width: 200px;
        height: 400px;
        border: 1px solid gray;
      }
    </style>
  </head>
  <body>
    <ul>
      <li>1-aaa</li>
      <li>2-bbb</li>
      <li>3i-ccc</li>
      <li>4i</li>
      <li>5i</li>
      <li>6i</li>
      <li>7i</li>
      <li>8i</li>
      <li>9i</li>
      <li>10i</li>
      <li>l1</li>
      <li>l2</li>
      <li>l3</li>
      <li>l4</li>
      <li>l5</li>
      <li>l6</li>
      <li>l7</li>
      <li>l8</li>
      <li>l9</li>
      <li>10</li>
      <li>21</li>
      <li>22</li>
      <li>23</li>
      <li>24</li>
      <li>25</li>
      <li>26</li>
      <li>27</li>
      <li>28</li>
      <li>29</li>
      <li>30</li>
    </ul>
    <script>
      const imgList = [...document.querySelectorAll("li")];
      const options = {
        root: null,
        rootMargin: "1px",
        thresholds: 1,
      };
      //io 为 IntersectionObserver对象 - 由IntersectionObserver()构造器创建
      const io = new IntersectionObserver((entries) => {
        //entries 为 IntersectionObserverEntry对像数组
        entries.forEach((item) => {
          //item 为 IntersectionObserverEntry对像
          // item.isIntersecting是一个Boolean值,判断目标元素当前是否可见
          if (item.isIntersecting) {
            console.log(item);
            // item.target.src = item.target.dataset.src;
            // li加载后即停止监听该元素
            io.unobserve(item.target);
          }
          // intersectionRatio === 1说明该元素完全暴露出来
          // if (item.intersectionRatio === 1) {
          //   埋点曝光代码
          //   do something...
          //   停止监听该元素
          //   io.unobserve(item.target);
          // }
        });
      }, options); //不传options参数,默认根元素为浏览器视口
      // observe遍历监听所有li节点
      imgList.forEach((li) => io.observe(li));
    </script>
  </body>
</html>

关于如何判断元素是否在可视区域内的其他方法

本地路径

第一种方法:offsetTop、scrollTop

公式: el.offsetTop - document.documentElement.scrollTop <= viewPortHeight

function isInViewPortOfOne (el) {
    // viewPortHeight 兼容所有浏览器写法
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const offsetTop = el.offsetTop
    const scrollTop = document.documentElement.scrollTop
    const top = offsetTop - scrollTop
    console.log('top', top)
     // 这里有个+100是为了提前加载+ 100
    return top <= viewPortHeight + 100
}

第二种方法:getBoundingClientRect

  • 返回值是一个 DOMRect 对象,拥有 left, top, right, bottom, x, y, width, 和 height 属性

公式: el.getBoundingClientReact().top <= viewPortHeight

其实, el.offsetTop - document.documentElement.scrollTop = el.getBoundingClientRect().top, 利用这点,我们可以用下面代码代替方法一

function isInViewPortOfTwo (el) {
    const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
    console.log('top', top)
    return top  <= viewPortHeight + 100
}

http://www.kler.cn/a/106441.html

相关文章:

  • 谷歌AI进军教育,这将改变未来?
  • 使用React和Vite构建一个AirBnb Experiences克隆网站
  • CSS基础知识01
  • Elastic Agent:可灵活地在任何地方发送和处理任何数据
  • 常用在汽车PKE无钥匙进入系统的高度集成SOC芯片:CSM2433
  • 【eNSP】路由基础与路由来源——静态路由实验
  • RK3568-pcie接口
  • LuatOS-SOC接口文档(air780E)--mcu - 封装mcu一些特殊操作
  • 如何在外网访问内网服务器数据库
  • 高通Quick Charge快速充电原理分析
  • Vue项目搭建及使用vue-cli创建项目、创建登录页面、与后台进行交互,以及安装和使用axios、qs和vue-axios
  • 在Linux中,可以使用以下命令来查看进程
  • tqdm 显示进度条模块
  • Echarts 实现 设备运行状态图(甘特图) 工业大数据展示
  • C++实现线程池
  • 软件工程第八周
  • 设计模式之中介模式
  • 2、基于pytorch lightning的fabric实现pytorch的多GPU训练和混合精度功能
  • Python学习笔记第六十九天(Matplotlib 直方图)
  • threejs(4)-纹理材质高级操作
  • 软件测试面试题
  • 面对6G时代 适合通信专业的 毕业设计题目
  • Unity Shader当用户靠近的时候会出现吃鸡一样的光墙
  • ARM,汇编指令
  • Linux MMC子系统 - 2.eMMC 5.1总线协议浅析
  • 关于前端如何下载后端接口返回content-type为application/octet-stream的文件