Open reboottime opened 1 year ago
import { useState, useEffect, useRef } from 'react';
export default function useLoadImg() {
const [state, setState] = useState<'loading' | 'loaded' | 'error'>();
const loadFuncRef = useRef<CallableFunction>(emptyFunc);
useEffect(() => {
loadFuncRef.current = async (src: string) => {
setState('loading');
try {
await loadImg(src);
setState('loaded');
} catch (e) {
setState('error');
}
}
}, []);
return {
state,
loadImg: loadFuncRef.current
};
}
function loadImg(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(void);
};
img.onerror = () => {
reject();
}
});
};
function emptyFunc (...args:any[]) {
}
const PLACEHOLDER_IMG_URL = '';
const LazyImage = ({ src, alt, className }) => {
const imgRef = useRef(null);
const { state: imgState, loadImg } = useLoadImg();
useEffect(() => {
const viewportHeight = window.innerHeight;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.boundingClientRect.top < 1.5 * viewportHeight) {
loadImg(src);
}
});
});
const imgElem = imgRef.current;
if (imgElem) {
observer.observe(imgElem);
}
return () => {
if (imgElem) {
observer.unobserve(imgElem);
}
};
}, [loadImg, src]);
const isVisible = imgState === 'loaded';
return (
<img
alt={alt}
ref={imgRef}
src={isVisible ? src : PLACEHOLDER_IMG_URL}
/>
);
};
Use case: Implementing "infinite scrolling" websites, where more and more content is loaded and rendered as you scroll, so that the user doesn't have to flip through pages.
import React, { useEffect, useRef, useState } from 'react';
const InfiniteScrollComponent: React.FC = () => {
const observer = useRef<IntersectionObserver | null>(null);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
// Initialize Intersection Observer when component mounts
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// When the observed element is in view, load more content
setIsLoading(true);
setPage(prevPage => prevPage + 1);
}
});
// Start observing the container element
if (containerRef.current) {
observer.current.observe(containerRef.current);
}
return () => {
// Clean up observer when component unmounts
if (observer.current) {
observer.current.disconnect();
}
};
}, []);
useEffect(() => {
// Simulate loading more content (you would fetch data from an API here)
if (isLoading) {
// Example fetch using window.fetch
fetch(`https://api.example.com/data?page=${page}`)
.then(response => response.json())
.then(data => {
// Handle the loaded data
console.log('Loaded data:', data);
setIsLoading(false);
})
.catch(error => {
console.error('Error fetching data:', error);
setIsLoading(false);
});
}
}, [isLoading, page]);
return (
<div>
<div>
{/* Render your content here */}
{/* ... */}
</div>
<div ref={containerRef}>
{/* This is the element that will be observed */}
</div>
</div>
);
};
export default InfiniteScrollComponent;
import React, { useRef, useEffect } from 'react';
import './AnimatedComponent.css'; // Create this file for your CSS animation
const AnimatedComponent = () => {
const animatedRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
entry.target.classList.add('animate'); // Add the CSS class for animation
observer.unobserve(entry.target); // Stop observing once animation is applied
}
},
{ threshold: 0.1 } // Adjust threshold as needed
);
if (animatedRef.current) {
observer.observe(animatedRef.current);
}
return () => {
if (animatedRef.current) {
observer.unobserve(animatedRef.current);
}
};
}, []);
return <div className="animated-element" ref={animatedRef}></div>;
};
export default AnimatedComponent;
Overview
This article talks about the application of Intersection Observer API and its compatibility consideration.
The basic API