この記事は CyberAgent 20 新卒 Advent Calendar 2019 の 22 日目です!
今回はタイトルにも書いてありますがintersection observerを使用してInfinite Scrollを実装したいと思います。
Intersection Observerとは
Intersection Observer API (交差監視 API) は、ターゲットとなる要素が、祖先要素もしくは文書の最上位のビューポートと交差する変更を非同期的に監視する方法を提供します。
https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API
useObserver
- intersection observerのcustom hook
import React, { useEffect, useState, RefObject } from 'react';
function useObserver(
ref: RefObject<HTMLDivElement>,
ops?: IntersectionObserverInit
) {
const [intersect, setIntersect] = useState<boolean>(false);
useEffect(() => {
function callback(entries: IntersectionObserverEntry[]) {
entries.forEach(entry => {
setIntersect(entry.isIntersecting);
});
}
const observer = new IntersectionObserver(callback, ops);
if (ref.current === null) {
return;
}
observer.observe(ref.current);
return () => {
if (ref.current !== null) {
observer.unobserve(ref.current);
}
};
});
return intersect;
}
使い方
export const Index: React.FC = () => {
const target = React.useRef<HTMLElement>(null);
const intersect = useObserver(target);
return (
<React.Fragment>
<div style={{ position: 'fixed', top: 0, left: '50%' }}>
{intersect ? 'visible' : 'not visible'}
</div>
<div style={{ height: 1000 }}></div>
<div ref={target}></div>
</React.Fragment>
);
};
- まず、ObserveしたいElementを
useObserver
に渡すために、ref
を作成します。
つぎに、ObserveしたいElementにprops
としてref
を渡せば完了です。
今回の例では、最初はref
を渡したDiv Elementが画面に表示されていないため、画面上部中央には、not visibleと表示されます。
画面をスクロールして、ref
を渡したDiv Elementが画面に表示されると、visibleと表示されます。
infinite scroll
今回実装したもの
ソースコード
import React, { useEffect, useState, RefObject } from 'react';
import axios from 'axios';
import './App.css';
interface Item {
id: number;
image: string;
}
const fetchItems = async (): Promise<Item[]> => {
const { data } = await axios.get('http://localhost:3000/item');
return data;
};
function useObserver(
ref: RefObject<HTMLElement>,
ops?: IntersectionObserverInit
) {
const [intersect, setIntersect] = useState<boolean>(false);
useEffect(() => {
function callback(entries: IntersectionObserverEntry[]) {
entries.forEach(entry => {
setIntersect(entry.isIntersecting);
});
}
const observer = new IntersectionObserver(callback, ops);
if (ref.current === null) {
return;
}
observer.observe(ref.current);
return () => {
if (ref.current !== null) {
observer.unobserve(ref.current);
}
};
});
return intersect;
}
export const App: React.FC = () => {
const [items, setItems] = useState<Item[]>([]);
const target = React.useRef<HTMLDivElement>(null);
const intersect = useObserver(target);
useEffect(() => {
async function fetch() {
const data = await fetchItems();
setItems(data);
}
fetch();
}, []);
useEffect(() => {
async function fetch() {
if (intersect) {
const data = await fetchItems();
setItems(prevItems => [...prevItems, ...data]);
}
}
fetch();
}, [intersect]);
return (
<React.Fragment>
<div className="container">
{items &&
items.map((item) => {
return (
<React.Fragment key={item.id}>
<div className="card">
<div className="card__item">
<img src={item.image}></img>
</div>
</div>
</React.Fragment>
);
})}
</div>
<div ref={target}>loading</div>
</React.Fragment>
);
};
export default App
おわりに
今回はintersection observerを使用してInfinite Scrollの実装を行いました.
intersection observerはその他にも,オフスクリーン時の画像の遅延読み込みやコンポーネントの遅延読み込みなどの使用法が考えられます.
要素をobserveして何かするという処理を書くことは結構あると思うので,積極的に使用していきましょう