React×IntersectionObserverで実現する3つの強力なフィーチャー！

要素がビューポート内にあるかどうかを監視するAPI、`IntersectionObserver`。これをReactと組み合わせれば、ユーザーエクスペリエンスを大幅に向上させることができます。この記事では、そんな組み合わせの力をフルに引き出す方法をご紹介します！

## 目次
1. IntersectionObserverとは？
2. ReactでIntersectionObserverを使うための基本
3. 実践！3つのフィーチャー
    - Lazy Loading
    - インフィニティスクロール
    - 広告の表示率計測
4. まとめ

## IntersectionObserverとは？
`IntersectionObserver`は、特定の要素がビューポート（表示領域）内に入ったり出たりしたときを監視するためのAPIです。これにより、要素の表示/非表示を効果的に制御したり、特定のアクションをトリガーできます。

## ReactでIntersectionObserverを使うための基本
Reactで`IntersectionObserver`を使う際は、主に`useRef`と`useEffect`を組み合わせます。`useRef`でDOM要素を取得し、`useEffect`内でその要素を監視するオブザーバーを設定します。

## 実践！3つのフィーチャー

### 1. Lazy Loading
Lazy Loadingは、要素（特に画像や動画）をページ読み込み時にすぐに読み込むのではなく、必要になったとき（例: スクロールして要素がビューポートに近づいたとき）に読み込む技術です。

#### コード例
```javascript
import React, { useState, useEffect, useRef } from 'react';

function LazyImage({ src, alt, ...props }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const imgRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        imgRef.current.src = src;
        imgRef.current.onload = () => setIsLoaded(true);
        observer.disconnect();
      }
    });

    observer.observe(imgRef.current);

    return () => observer.disconnect();
  }, [src]);

  return <img ref={imgRef} alt={alt} {...props} style={isLoaded ? {} : { opacity: 0 }} />;
}
```

#### 解説
- `useState`で画像の読み込み状態を管理します。
- `useRef`を使って画像要素への参照を保持します。
- `useEffect`内で、要素がビューポート内に入ったかどうかを監視するオブザーバーを設定します。
- 画像がビューポート内に入ると、実際の`src`を設定し、画像を読み込みます。

### 2. インフィニティスクロール
ページの最下部に達したときに、次のコンテンツを自動的に読み込む技術です。SNSなどでよく見られます。

#### コード例

```javascript
import React, { useState, useEffect, useRef } from 'react';

function InfinityScroll({ fetchData }) {
  const [items, setItems] = useState([]);
  const loadingRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(async ([entry]) => {
      if (entry.isIntersecting) {
        const newItems = await fetchData();
        setItems(prevItems => [...prevItems, ...newItems]);
      }
    });

    if (loadingRef.current) {
      observer.observe(loadingRef.current);
    }

    return () => observer.disconnect();
  }, [fetchData]);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
      <div ref={loadingRef}>Loading...</div>
    </div>
  );
}
```

#### 解説
- 読み込み中のステータスを表示する要素（この例では"Loading..."）を監視します。
- この要素がビューポート内に入ると、次のデータをフェッチし、既存のアイテムリストに追加します。

### 3. 広告の表示率計測
広告がビューポートにどれだけの時間表示されていたかを計測します。これは、広告の課金基準などで使用されることがあります。

#### コード例

```javascript
import React, { useEffect, useRef } from 'react';

function AdComponent({ src }) {
  const adRef = useRef(null);
  const startTimeRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        startTimeRef.current = new Date();
      } else if (startTimeRef.current) {
        const duration = new Date() - startTimeRef.current;
        console.log(`Ad was visible for ${duration}ms`);
        startTimeRef.current = null;
      }
    });

    if (adRef.current) {
      observer.observe(adRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return <img ref={adRef} src={src} alt="Advertisement" />;
}
```

#### 解説
- 広告がビューポートに表示された開始時間を記録します。
- 広告がビューポートから出ると、表示された時間を計算し、それをログに出力します。

## まとめ
Reactと`IntersectionObserver`を組み合わせることで、非常にパワフルなフィーチャーを簡単に実装できます。上記の例を参考にして、自分のプロジェクトに取り入れてみてください！