IntersectionObserver 基礎
IntersectionObserver 可以監聽一個元素和可視區域相交部分的比例,然后在可視比例達到某個閾值的時候觸發回調。比如可以用來處理圖片的懶加載等等
首先我們來看下基本的格式:
const observer = new IntersectionObserver(callback, [options]);
相關的API屬性和方法:
直接看他的Typescript結構吧
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
的計算
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
const observer = new IntersectionObserver(entries => {// 處理entries},{thresholds: [0, 0.25, 0.5, 0.75, 1]}
);
disconnect
用于停止監聽目標元素與根元素的交叉變化。當你不再需要觀察元素的可見性變化時,可以調用disconnect
方法來停止IntersectionObserver
的所有活動。
調用disconnect
方法后,IntersectionObserver
將不再觸發任何回調,即使目標元素的可見性發生變化。這意味著,你已經不再對目標元素的可見性感興趣,或者你想要在組件卸載時清理資源。
// 創建一個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
方法來指定需要觀察的元素
// 創建一個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,可以做必要的安裝哦!
下面是一個設置一個組件,看如下代碼
/** @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; // 拿到nodeif (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;
組件的調用:
/** @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>{/* 這邊增加一些類名可以做一些的動畫 */}<LazyLoadclassName={isVisible ? "show" : "hide"}placeholder={<div>loading...</div>}onContentVisible={() => {console.log("comp visible");setIsVisible(true);}}>{/* <img src={img1}/> */}</LazyLoad><LazyLoadplaceholder={<div>loading...</div>}onContentVisible={() => {console.log("img visible");}}><img src={img2} /></LazyLoad></div>);
}export default App;
我們看最后的效果:
當剛進入頁面的時候,我們下面的元素都處于 loading中,也是上面的placeholder的占位內容。
當滑動圖片的位置的時候才加載出相對應的圖片地址和對應的類名