この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
西田@大阪@MAD事業部です
Reactでユーザーがどこまでコンテンツを見たか、既読管理をする仕組みを調べてみました
ユーザーがコンテンツを見たか判定する仕組み
コンテンツが viewport に入ったらユーザーがコンテンツを見たと判定します。viewportにコンテンツが入ったことを検知するために Intersection Observer API
を使用します
構成
コンテンツがviewportに半分入ったらコールバック関数を呼び出すコンポーネント Container.tsx を作成し、それを App.tsx でインポートして使用してます
完成イメージ
ソースコード
src/Container.tsx
import React, { useEffect } from 'react';
type Props = {
index: number;
onIntersection?: (index: number) => void;
};
const Container: React.FC<Props> = ({ index, onIntersection, children }) => {
const ref = React.useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (onIntersection !== undefined) {
onIntersection(index);
}
}
},
{ threshold: 0.5 },
);
if (ref.current === null) return;
observer.observe(ref.current);
const { current } = ref;
return () => {
observer.unobserve(current);
};
}, []);
return <div ref={ref}>{children}</div>;
};
export default Container;
src/App.tsx
import React, { useState } from 'react';
import './App.css';
import Container from './Container';
const App: React.FC = () => {
const ref = React.useRef<HTMLDivElement>(null);
const [progress, setProgress] = useState(0);
const intersectCallback = (index: number) => {
setProgress(index);
};
const items = [1, 2, 3];
return (
<div>
<header className="header">{progress} まで読んだ</header>
{items.map((i) => (
<Container index={i} onIntersection={intersectCallback}>
<div ref={ref} key={i} className="contents">
{i}
</div>
</Container>
))}
</div>
);
};
export default App;
ソースコードの説明
ReactでDOMを操作するために userRef
hooks を使用します
const ref = React.useRef<HTMLDivElement>(null);
IntersectionObserver のコンストラクタにコンテンツが viewport に入った時に呼ばれるコールバックと、オプションでコンテンツが半分(0.5)入ったらコールバックが呼ばれるように指定しています
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
if (onIntersection !== undefined) {
onIntersection(index);
}
}
},
{ threshold: 0.5 },
);
IntersectionObserver に ref.current
でDOMの参照を渡します
observer.observe(ref.current);
ref
を検知したい要素に設定します
return <div ref={ref}>{children}</div>;
補足
この記事を書くにあたり調べたことをメモ程度に残しておきます
Intersection Observer API
指定されたHTMLの要素が viewport 、または、特定の要素と交差した(要素と要素が重なった)場合にコールバック関数を実行します
オプション
- root
- コンテンツの要素が交差したかどうかを判定するための要素を指定します。nullを指定した場合(デフォルト)は viewport が使用されます
- rootMargin
- 交差の判定に使われる root のマージンを指定します。正の値を指定すると root の交差範囲が広がりコンテンツが見えるよりも早くコールバックが実行され、負の値を指定すると root の交差範囲が狭まり、コンテンツが見え始めてから少し遅れてコールバックが実行されます
- threshold
- どのくらいコンテンツが見えたらコールバックを実行するかを 0 〜 1 の間で指定します
対応ブラウザ
IEでは対応していませんが、主だったブラウザでは対応してます
https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API#browser_compatibility
viewport
viewportはHTMLが表示される領域のことです。ブラウザのウィンドウサイズを変更すれば、viewportもかわり、スマフォのviewportは狭いです
参考
JSでのスクロール連動エフェクトにはIntersection Observerが便利 - ICS MEDIA
ユーザーが画面内のコンテンツを見たかどうかを判定する一歩上の仕組み、サービスでの活用事例 - Qiita
もう逃げない。HTMLのviewportをちゃんと理解する - Qiita
viewportを理解して正しいレスポンシブデザインを設定しよう | デジタルマーケティング・Web制作・PR支援のBigmac inc