React でどこまでユーザーがコンテンツを見たか既読管理してみる

Reactでユーザーがどこまでコンテンツを見たか、既読管理をする仕組みを調べてみました
2021.10.07

この記事は公開されてから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