IntersectionObserver的那一二件事

IntersectionObserver 基础

IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。比如可以用来处理图片的懒加载等等

首先我们来看下基本的格式:

ini 复制代码
const observer = new IntersectionObserver(callback, [options]);

相关的API属性和方法:

直接看他的Typescript结构吧

php 复制代码
interface IntersectionObserver {
    // root 属性用来获取当前 intersectionObserver 实例的根元素
    readonly root: Element | Document | null;
   
    readonly rootMargin: string;
    
    readonly thresholds: ReadonlyArray<number>;
    
    disconnect(): void;
   
    observe(target: Element): void;
   
    takeRecords(): IntersectionObserverEntry[];
    
    unobserve(target: Element): void;
}

root: 如果构造函数未传入 root 或其值为null,则默认使用顶级当前文档的视口。

rootMargin : 是 IntersectionObserver 构造函数的一个可选属性,它定义了一个矩形区域,用于扩展或缩小root元素的可视区域,从而影响intersectionRatio的计算

dart 复制代码
const observer = new IntersectionObserver(
  entries => {
    // 处理entries
  },
  {
    root: document.querySelector('#scrollArea'), // 根元素 || 顶级当前文档
    rootMargin: '50px 20px 30px 10px' // 上右下左
  }
);

thresholds:,它定义了一个监听交叉变化时触发回调的阈值列表。这些阈值是介于0和1之间的数值,包括0和1,表示目标元素与根元素相交的比例。举个例子,当创建一个IntersectionObserver实例时,你可以指定一个或多个阈值。例如,如果你想要在目标元素至少有25%、50%和75%可见时触发回调,你可以这样设置thresholds

ini 复制代码
const observer = new IntersectionObserver(
  entries => {
    // 处理entries
  },
  {
    thresholds: [0, 0.25, 0.5, 0.75, 1]
  }
);

disconnect用于停止监听目标元素与根元素的交叉变化。当你不再需要观察元素的可见性变化时,可以调用disconnect方法来停止IntersectionObserver的所有活动。

调用disconnect方法后,IntersectionObserver将不再触发任何回调,即使目标元素的可见性发生变化。这意味着,你已经不再对目标元素的可见性感兴趣,或者你想要在组件卸载时清理资源。

javascript 复制代码
// 创建一个IntersectionObserver实例
const observer = new IntersectionObserver(function(entries) {
  // 处理交叉变化
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      console.log('元素现在可见');
    } else {
      console.log('元素不再可见');
    }
  });
});
​
// 开始观察一个元素
const target = document.querySelector('#my-element');
observer.observe(target);
​
// ...一段时间后...
​
// 停止观察元素
observer.disconnect();

observer: 用于开始监听一个目标元素与根元素的交叉变化。当你想要知道一个元素是否进入了视口(即用户的可见区域)时,你可以使用observe方法来指定需要观察的元素

javascript 复制代码
// 创建一个IntersectionObserver实例
const observer = new IntersectionObserver(function(entries) {
  // 处理交叉变化
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      console.log('元素现在可见');
    } else {
      console.log('元素不再可见');
    }
  });
});
​
// 获取要观察的元素
const target = document.querySelector('#my-element');
​
// 开始观察元素
observer.observe(target);

takeRecords:用于获取并清空IntersectionObserver实例的记录队列。这个方法返回一个IntersectionObserverEntry对象的数组,每个对象描述了目标元素的相交状态

unobserve:用于停止监听特定目标元素与根元素的交叉变化。当你不再需要监听某个元素的可见性变化时,你可以使用unobserve方法来停止对该元素的观察。

综合案例,实现图片的懒加载

下面的方法使用的react,可以做必要的安装哦!

下面是一个设置一个组件,看如下代码

ini 复制代码
/*
 * @Date: 2024-05-28 09:59:48
 * @Description: 组件的设计
 */
import { CSSProperties, FC, ReactNode, useEffect, useRef, useState } from "react";
​
interface MyLazyloadProps {
  className?: string; /* className 和 style 是给外层 div 添加样式的 */
  style?: CSSProperties;
  placeholder?: ReactNode; /* 是占位的内容 */
  offset?: string | number; /* 是距离到可视区域多远就触发加载 */
  width?: number | string;
  height?: string | number;
  onContentVisible?: () => void; /* 进入可视化区域后所产生的回调 */
  children: ReactNode;
}
​
const MyLazyload: FC<MyLazyloadProps> = (props) => {
  const { className = "", style, offset = 0, width, onContentVisible, placeholder, height, children } = props;
​
  const containerRef = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);
​
  const elementObserver = useRef<IntersectionObserver>();
​
  /* 关键函数去判断可视范围 */
  const lazyLoadHandler = (entries: IntersectionObserverEntry[]) => {
​
    const [entry] = entries;
    const { isIntersecting, intersectionRatio } = entry;
    
    if (intersectionRatio > 0) {
      const node = containerRef.current;
      console.log(node, entry, intersectionRatio);
    }
​
    if (isIntersecting) {
      setVisible(true);
      /* 可以通过这一层函数传递给外部,然后通过这个函数,可以在外部组件做相对应的处理等等 */
      onContentVisible?.();
​
      const node = containerRef.current;
      // 展示完成后及时的销毁
      if (node && node instanceof HTMLElement) {
        elementObserver.current?.unobserve(node);
      }
    }
  }
​
  useEffect(() => {
    const options = {
        /* 这边没有写root,则这边的根元素就是此文档的 containerRef */
        /* rootMargin 这边做了一次偏移处理 */
        rootMargin: typeof offset === 'number' ? `${offset}px` : offset || '0px',
        /* 设置 threshold 为 0 也就是一进入可视区域就触发 */
        threshold: 0,
    }
​
    elementObserver.current = new IntersectionObserver(lazyLoadHandler, options);
​
    const node = containerRef.current; // 拿到node
​
    if (node instanceof HTMLElement) {
        elementObserver.current.observe(node);
    }
    return () => {
        if (node && node instanceof HTMLElement) {
            elementObserver.current?.unobserve(node);
        }
    }
  }, []);
​
  const styles = { height, width, ...style };
​
  return (
    <div ref={containerRef} className={`${className}`} style={styles}>
      {visible ? children : placeholder}
    </div>
  );
};
​
export default MyLazyload;
​
​

组件的调用:

javascript 复制代码
/*
 * @Date: 2024-05-27 11:21:07
 * @Description: 组件的调用
 */
import { useState } from "react";
import img1 from "./素材1.png";
import img2 from "./扑克牌1.jpg";
import "./App.css";
// import LazyLoad from 'react-lazyload';
import LazyLoad from "./MyLazyLoad";
​
function App() {
  const [isVisible, setIsVisible] = useState<boolean>(false);
  return (
    <div>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      <p>一一一一一一一一一一一一一一一一一一</p>
      {/* 这边增加一些类名可以做一些的动画 */}
      <LazyLoad
        className={isVisible ? "show" : "hide"}
        placeholder={<div>loading...</div>}
        onContentVisible={() => {
          console.log("comp visible");
          setIsVisible(true);
        }}
      >
        {/* <img src={img1}/> */}
      </LazyLoad>
      <LazyLoad
        placeholder={<div>loading...</div>}
        onContentVisible={() => {
          console.log("img visible");
        }}
      >
        <img src={img2} />
      </LazyLoad>
    </div>
  );
}
​
export default App;
​

我们看最后的效果:

当刚进入页面的时候,我们下面的元素都处于 loading中,也是上面的placeholder的占位内容。

当滑动图片的位置的时候才加载出相对应的图片地址和对应的类名

相关推荐
汪子熙18 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ27 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试