ResizeObserver和IntersectionObserver的详细讲解

ResizeObserver 的介绍

ResizeObserver 用于异步观察元素的尺寸变化。

如:SVG 元素或文本节点的大小变化、调整浏览器窗口大小、动态改变某个元素的大小时

可以触发相应的回调处理逻辑。

当这些目标的大小变化时,ResizeObserver 将会触发一个回调函数

特别提醒:ResizeObserver 在初始观察元素时就会触发回调

Resize 读音:/ˌriːˈsaɪ z/

Observer 读音:/əbˈzɜː və(r)/

ResizeObserver的基本语法

javascript 复制代码
const target = document.getElementById('xx')
ResizeObserver.observe(target,{box: "content-box"})

target 表示需要观察的元素,必须填写

box属性:指定监听的盒模型类型,可选值。包括有下面的值

"content-box"(默认值):监听内容区域尺寸变化

"border-box":监听包含边框和内边距的尺寸变化

"device-pixel-content-box":监听设备像素相关的尺寸变化(适用于高精度场景)

ResizeObserver的基本使用

1,首先获取需要被观察的元素

2,创建实例对象,对象发生变化后需要处理的业务逻辑

javascript 复制代码
const ResizeObserver = new ResizeObserver((entriesArr)=>{
    entriesArr.forEach(entry => {
      console.log('变化元素尺寸需要处理的业务逻辑:', entry.contentRect);
    });
   })

3,最后通过resizeObserver.observe(target) 进行进行观察

下面请看如何具体的使用

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      padding: 0;
      margin: 0;
    }
    #divElement{
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      background-color: pink;
    }
  </style>
</head>
<body>
  <div id="divElement">我是一个元素</div>
</body>
<script>
// 获取需要被观察的元素(被观察的对象)
const divElement = document.getElementById('divElement')
// 创建一个观察器实例并传入回调函数
const resizeObserver = new ResizeObserver(entriesArr => {
  for (let entry of entriesArr) {
    console.log('Element:', entry.target);
    // 输出元素的宽度和高度
    console.log('New size:',entry.contentRect.width, 'x', entry.contentRect.height);
  }
});
// 观察一个指定的元素
resizeObserver.observe(divElement);
</script>
</html>

ResizeObserver 在初始观察元素时就会触发回调?

是的,在初始观察元素时。

ResizeObserver 会立即执行一次回调,以提供元素的初始尺寸。

这样设计的好处是:我们可以立即获取到元素的尺寸。

ResizeObserver如何一次性监听多个目标

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      padding: 0;
      margin: 0;
    }
    .box{
      display: flex;
    }
    .ele{
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      background-color: pink;
      margin-left: 5px;
      margin-right: 5px;
    }
  </style>
</head>
<body>
  <div class="box">
    <div class="ele">我是第1个元素</div>
    <div class="ele">我是第2个元素</div>
    <div class="ele">我是第3个元素</div>
  </div>
  <button id="btn">停止观察</button>
</body>
<script>
const observer = new ResizeObserver(entriesArr => {
  // entriesArr 是一个数组,表示的是每一个被观察的元素
  console.log("entriesArr", entriesArr)
  entriesArr.forEach(item => {
    console.log(`元素尺寸:${item.contentRect.width} x ${item.contentRect.height}`);
  });
});
  // 监听多个元素
document.querySelectorAll('.ele').forEach(elem => {
  observer.observe(elem);
});

// 点击按钮后,会停止观察所有的元素
const btn=document.getElementById('btn')
btn.addEventListener('click',()=>{
  observer.disconnect()
})
</script>
</html>

停止观察特定的目标元素

有些时候我们不需要在观察某个特定的目标

此时,可以停止观察这个特定的目标,它对于性能优化有非常大的作用。

语法:resizeObserver.unobserve("停止观察的元素");

当我们缩放屏幕的时候,我们可以发现在控制台会有输出元素的宽高。

当我们点击停止观察后,控制台不会在有输出,因为我们停止观察这个元素了。

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      padding: 0;
      margin: 0;
    }
    #divElement{
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      background-color: pink;
    }
  </style>
</head>
<body>
  <div id="divElement">我是一个元素</div>
  <button id="btn">停止观察</button>
</body>
<script>
// 获取需要被观察的元素(被观察的对象)
const divElement = document.getElementById('divElement')
// 创建一个观察器实例并传入回调函数
const resizeObserver = new ResizeObserver(entries => {
  for (let entry of entries) {
    console.log('Element:', entry.target);
    console.log('New size:',entry.contentRect.width, 'x', entry.contentRect.height);
  }
});
// 观察一个指定的元素
resizeObserver.observe(divElement);

// 点击按钮后,会停止观察这个元素
const btn=document.getElementById('btn')
btn.addEventListener('click',()=>{
  resizeObserver.unobserve(divElement)
})
</script>
</html>

disconnect 停止观察所有目标元素

disconnect可以停止观察所有目标元素。

效果与unobserve相同

他们之间的区别是:disconnect 是停止所有的。

unobserve是停止特定的某个元素

javascript 复制代码
...其他代码保持不变
// 点击按钮后,停止观察所有目标元素
const btn=document.getElementById('btn')
btn.addEventListener('click',()=>{
  resizeObserver.disconnect()
})

contentRect 的含义

细心的小伙伴发现了,我们在获取宽高的使用使用了 xxx.contentRect.width来获取。

这个contentRect 是啥东西?

其实:contentRect 是一个 DOMRectReadOnly 对象,包含以下只读属性

width:元素内容区域的宽度(不含 padding/border)

height:元素内容区域的高度

x/y:内容区域相对于根元素(或视口)的坐标

top/left/right/bottom:内容区域的边界位置

低版本浏览器如何处理

如果担心ResizeObserver的兼容性可使用resize-observer-polyfill这个插件

首先: npm install resize-observer-polyfill --save-dev

使用如下

javascript 复制代码
import ResizeObserver from 'resize-observer-polyfill';

const plugInObserver = new ResizeObserver((entriesArr, observer) => {
    for (const entry of entriesArr) {
      const { contentRect } = entry; // 这里使用了结构的哈
      console.log('Element resized:', contentRect);
    }
});
 
// 获取需要监听的DOM元素
const element = document.getElementById('yourElement');
plugInObserver.observe(element);

用节流提升性能

如果触发的很频繁的话,我们可以使用节流的方式来降低触发频率

javascript 复制代码
function throttle(func, delay) {
    let begin = 0;
    return function () {
        var cur = new Date().getTime();
        if (cur - begin > delay) {
            func.apply(this, arguments)
            begin = cur
        }
    }
}

const myObserver = new ResizeObserver(throttle(entriesArr=> {
  entriesArr.forEach(entry => {
    console.log('大小位置 contentRect', entry.contentRect)
    console.log('监听的DOM target', entry.target)
  })
}), 200)

IntersectionObserver的简单介绍

IntersectionObserver API是用来观察元素是否可见,通常被我们叫做:"交叉观察器"。

和 ResizeObserver 一样,在初始观察元素时就会触发回调函数

Intersection 读音:/ɪn tə ˈse k ʃn/ 十字路口,交叉,交点

ResizeObserver 的观察配置

ResizeObserver 有3个非常重要的配置项。

javascript 复制代码
let options = {
  root: null, // 观察目标的上层元素。如:父级元素。爷爷元素。默认为视口。
  rootMargin: '0px', // 视口向外扩展的距离
  threshold: 0.1 
  // 观察对象与视口交叉的范围,取值[0,1]。0表示碰上。1表示要完全看见。
  // 0.1表示:目标元素与视口可见比例达到 10% 时触发回调
};
 
let watchObject = new IntersectionObserver(()=>{
  console.log('触发时执行的回调函数')
}, options);

rootMargin视口向外扩展的距离

IntersectionObserver的使用场景

1,懒加载图片

2,无限滚动加载

3,展示加载动画

下面我们就使用IntersectionObserver实现懒加载。

让大伙更加熟悉这个api的实验

停止观察

disconnect() 停止监视所有目标元素的可见性变化并销毁观察者。

unobserve(targetElement) 停止观察指定的目标元素

静态布局

静态页面,先写一下图片的布局。 图片的src设置为空,真实的地址在自定义属性data-rsc中。

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
   <style>
    *{
      padding: 0;
      margin: 0;
    }
    .img-max-box{
      display: flex;
      flex-wrap: wrap;
      margin-top: 20px;
    }
    .img-container{
      width: 484px;
      margin-left: 20px;
      height: 300px;
      margin-bottom: 20px;
    }
    img{
     width: 100%;
     height: 100%;
    }
   </style>
</head>
<body>
  <div class="img-max-box">
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
  </div>
</body>
<script>
</script>
</html>

使用IntersectionObserver实现懒加载

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
   <style>
    *{
      padding: 0;
      margin: 0;
    }
    .img-max-box{
      display: flex;
      flex-wrap: wrap;
      margin-top: 20px;
    }
    .img-container{
      width: 484px;
      margin-left: 20px;
      height: 300px;
      margin-bottom: 20px;
    }
    img{
     width: 100%;
     height: 100%;
    }
   </style>
</head>
<body>
  <div class="img-max-box">
    <div class="img-container">
      <img src="" data-src="https://plus.unsplash.com/premium_photo-1666278379770-440439b08656?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8YW5pbWFsc3xlbnwwfHwwfHx8MA%3D%3D"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8YW5pbWFsc3xlbnwwfHwwfHx8MA%3D%3D"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
    <div class="img-container">
      <img src="" data-src="https://images.pexels.com/photos/106399/pexels-photo-106399.jpeg"/>
    </div>
  </div>
</body>
<script>
  const watchObject = new IntersectionObserver((entriesArr)=>{
    console.log('触发了这个函数', entriesArr);
    // entriesArr 是一个数组。里面是被观察的元素。目前我有20个图片。因此数组的长度为20
    entriesArr.forEach((entryItem)=>{
      // entryItem 是一个对象。里面有isIntersecting属性。表示是否与视口交叉。true表示交叉了。 false表示没有交叉。
      if(entryItem.isIntersecting){
        console.log('交叉了');
        entryItem.target.src = entryItem.target.dataset.src;
        // 停止观察这个元素,因为这个元素已经获取到图片了
        watchObject.unobserve(entryItem.target);
      }
    })
  },{
    threshold: 0 // 观察对象与视口交叉的范围。取值[0,1],0表示碰上就会执行回调。1表示要完全看见。
  })

  const imgArr = document.querySelectorAll('img');
  imgArr.forEach((imgItem)=>{
    // 开始进行观察
    watchObject.observe(imgItem);
  })
</script>
</html>

intersectionRatio,isIntersecting,target,time属性的详细讲解

intersectionRatio:number

表示目标元素与根元素的交叉区域的比例,即交叉区域面积与目标元素总面积的比值。

当目标元素完全可见时,值为1;完全不可见时,值为0。返回的是一个类型的值。

如果元素部分可见,则是一个介于0和1之间的值。

isIntersecting:boolean

表示目标元素是否与根元素相交(交叉)。如果相交(即使交叉区域为零)则为true;否则为false。

true: 当元素开始进入或仍在交叉区域

false: 当元素完全离开交叉区域

target:Element

表示:被观察的目标元素。

用途: 在多个观察目标中识别当前触发的元素

time: number

描述: 交叉状态变化的时间戳(从页面加载开始计算,单位:毫秒)

用途:1,计算交叉事件的精确时间。2,性能分析(如计算元素进入视口的耗时)

相关推荐
魔都吴所谓1 分钟前
[前端]HTML模拟实现一个基于摄像头的手势识别交互页面
前端·html·交互
来自星星的猫教授3 分钟前
Vue 3.6前瞻:响应式性能革命与Vapor模式展望
前端·javascript·vue.js
涵信7 分钟前
第九节 高频代码题-实现Sleep函数(异步控制)
前端·javascript·typescript
Kusunoki_D21 分钟前
Python 实现 Web 静态服务器(HTTP 协议)
服务器·前端·python
爱学习的茄子31 分钟前
【前端实战】三分钟掌握原生JS电影搜索应用,从此告别框架依赖
前端·javascript·深度学习
林太白33 分钟前
Next.js超简洁完整篇
前端·后端·react.js
前端付豪33 分钟前
汇丰登录风控体系拆解:一次 FaceID 被模拟攻击的调查纪实
前端·后端·架构
用户38022585982437 分钟前
Vue3源码解析:深度解读ref实现源码
vue.js
天生我材必有用_吴用41 分钟前
Three.js开发必备:模型对象和材质详解
前端
万变不离其宗_841 分钟前
echarts使用笔记
前端·笔记·echarts