🗯️

ブログ記事を読み終わったらポップアップを出すやつの備忘録(React)

に公開

今回、ブログ記事を読み終わったらポップアップを出すという実装を行ったのでその実装方法備忘録です。

作りたいもの

  • 一定の位置にスクロールされたらポップアップを出す(今回だとブログ記事が読み終わったタイミング)
  • ポップアップが出るのは一度切り

開発環境

  • TypeScript
  • Next.js(13)
  • MUI (ポップアップ自体はmuiで実装)

開発〜その①〜

今回の実装方針として、以下のような道筋を立てました。

  1. イベントリスナーでスクロールを検知
  2. 画面の底がtopから一定のpxまで来たらポップアップを表示
  3. ポップアップを閉じる際に二度とポップアップが開かないようにポップアップ管理用のstate(boolean)をtrue→falseへ変更

実装

方針も定まったところで実装!!

全て書くと多くなるので、スクロールの度にブラウザのそこの位置を計測する関数のみ抜粋して記載します。

Popup.tsx
       ・
    ・
    ・

// ブラウザTOPの位置を取得
const scrollTop = (): number => {
  return Math.max(
    window.pageYOffset,
    document.documentElement.scrollTop,
    document.body.scrollTop,
  )
}

// ブラウザ底部の位置を取得
const getScrollBottom = () => {
  const ScrollTop = scrollTop()
  const screenHeight = window.innerHeight
  const scrollBottom = ScrollTop + screenHeight
  return scrollBottom
}

document.body.scrollBottom
なんてものがあれば一発ですが、そんなものはないので地道に

ブラウザTOPの位置+ブラウザの高さ=ブラウザ底部の位置

というやり方でブラウザ底部の位置を算出します。
あとはwindow.addEventListenerでスクロールの度にブラウザ底部の位置をチェックし、任意の位置より下に来たらポップアップをオープンするようにすれば完成!!!??

と思いましたが、ここで重大なことに気づきます…
記事の終わりの位置ってPC表示時とスマホ表示時で高さ違うじゃん!!!

そう、同じ記事を読んだとしてもスマホの方が横幅が狭い分、記事の最終位置はPCの時より下になってしまうのです!!
そもそも、1記事ならともかく、100近く記事があるような場合だと100記事分の記事終わり位置をpxで計測する必要があるためこの方法はそもそも面倒なやり方ですね。

ということで別の方法を考えます。

開発〜その②〜

pxによる位置指定でのポップアップの発火方法が無理であるなら、記事終了位置のエレメントが画面上に表示されたら発火するやり方しかないですね。

そんな機能を探していたところこの記事を見つけました。

【React】あるHTML要素が画面内に表示されている時だけtrueを返すようなhooksを作る(Qitta)

IntersectionObserver

どうやらこいつを使えば画面内にrefObjectが表示されているかどうかわかるらしいですね。

というわけで道順を以下に修正です。

  1. イベントリスナーでスクロールを検知
  2. 記事の終わりの位置にあるエレメントが画面に表示されたらポップアップを表示
  3. ポップアップを閉じる際に二度とポップアップが開かないようにポップアップ管理用のstate(boolean)をtrue→falseへ変更

IntersectionObserverで再実装

ということでこんな感じで実装し直しました。




   
const [isScrollOver, setIsScrollOver] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)

const observerRef = useRef<HTMLDivElement>(null)
  const [inView, setInView] = useState(false)

  const checkInView = (ref: RefObject<HTMLElement>) => {
    if (!ref.current) {
      return
    }
    const observer = new IntersectionObserver(([entry]) =>
      setInView(entry.isIntersecting),
    )
    observer.observe(ref.current)
    return () => {
      observer.disconnect()
    }
  }
  
 useEffect(() => {
    const onScroll = () => {
      if (overRef.current) return
      const scrollBottom = getScrollBottom()
      if (scrollBottom >= px) {
      checkInView(observerRef)
      if (inView) {
        setIsModalOpen(true)
        setIsScrollOver(false)
      }
    }
    window.addEventListener('scroll', onScroll)
  }, [isScrollOver, inView])



  

これで記事の最終位置が変わっても任意のコンポーネントが表示されたら発火する普遍的なコンポーネントが出来上がりました!

終わりに

スクロールに合わせて表示が変わるのはメディア系のサイトであれば頻出の技術ですよね!

噂によるとEventListenerでスクロールイベントをキャッチしなくてもIntersectionObserverだけで任意のコンポーネントが表示されれば状態を変えることができるらしいのです。

スクロールのトリガーは便利な分、スクロールの度に関数が実行されてしまうというデメリットもあります。

より高速な表示速度実現のため、さらにIntersectionObserver等の理解を深めていきたいです!!

稚拙な記事失礼いたしました m(_ _)m

Discussion