🐷

Intersection Observer APIを使ってスクロール後に下部メニューを表示させる

2023/05/09に公開

はじめに

ページ上部に配置されたメニューバーをスクロースによって画面追従する際、通常は追従させる要素にstickyなどを付与して画面追従させるのが普通かと思います。

画面上部でついてくるこれです。

ですが、イレギュラーではありますが追従させる要素がスクロールによって画面から隠れた後、下部にその要素を配置するなどの要望があった際、window.scroll等を使うなどの方法がありますが、画面サイズによっては正常に動作しないなどの弊害があります。
今回はそんな要望が来た際に、要素の監視を行うIntersection Observer APIというものを使うことによって楽に実装できる方法がありましたので書いておこうと思います。

Intersection Observer API とは

Intersection Observer APIは指定した要素が画面内に入っているか検知するJavascriptAPIになります。このIntersection Observer APIの使用例として以下のような使い方があります。

  • ページがスクロールした際の画像やその他のコンテンツの遅延読み込み。
  • 「無限スクロール」をするウェブサイトを実装し、スクロールに従って次々とコンテンツを読み込んで、ユーザーがページの切り替えをせずに済むようにすること。
  • 広告費を計算するための広告が表示されたかどうかのレポート。
    ユーザーが結果を見るかどうかで、タスクを実行するかどうか、アニメーションを処理するかどうかを決定すること。

https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API

やってみる

実際にどんな動作をするか見た方が早いので早速やってみます。
今回は読み替えしやすいようにVueやReact、TypeScriptなどを使用せず古のHTMLとJavascriptで書いていこうと思います。決して環境構築が面倒くさいとかではないです。全然そんなことないです。

まずは画面から。HTMLとCSSのコードになります。

<div class="container">
    <nav id="header">
        <ul>
            <li><a href="#">HOME</a></li>
            <li><a href="#">メニュー1</a></li>
            <li><a href="#">メニュー2</a></li>
        </ul>
    </nav>

    <nav hidden id="under-menu">
        <ul>
            <li><a href="#">HOME</a></li>
            <li><a href="#">メニュー1</a></li>
            <li><a href="#">メニュー2</a></li>
        </ul>
    </nav>

    <div class="main">
        これはメインコンテンツです。
    </div>
</div>

<style>
    body {
        margin: 0;
    }

    .container {
        width: 100%;
    }

    nav {
        width: 100%;
        height: 70px;
        background-color: dimgray;
        padding-top: 5px;
        box-sizing: border-box;
    }

    ul {
        display: flex;
    }

    li {
        list-style: none;
    }

    a {
        display: block;
        text-decoration: none;
        color: white;
        margin-right: 35px;
    }

    #under-menu {
        position:fixed;
        bottom: 0;
        width: 100%;
    }

    .main {
        height: 2000px;
        background: gainsboro;
    }
</style>

こんな感じの画面が出来上がりました。
次は画面上部に表示されているヘッダーを、画面スクロールによって隠れてしまった際に下部に表示させて追従するように処理を書いてみます。

こちらが実際にIntersection Observer APIを使用したスクリプト。
ヘッダーが画面スクロールによって隠れたら下部に配置しているhidden属性のメニューを表示させるように切り替える単純な処理になります。

<script>
    const ele = document.querySelector("#header");
    const ele2 = document.querySelector("#under-menu")
    // オプションを指定する事ができる。
    const options = {
        threshold: 0
    };
    const onScreen = (entries) => {
	// InterSectionObserverEntryを参照し、表示・非表示を切り替える
        if(entries[0].isIntersecting){
            ele2.hidden = true;
        }
        else if(!entries[0].isIntersecting){
            ele2.hidden = false;
        }
    }
    const observer = new IntersectionObserver(onScreen, options);
    observer.observe(ele);
</script>

Intersection Observer APIはオプションを指定して動作の調整を行う事ができます。
下記にオプションで指定できるプロパティを記述します。

root:
ターゲットが見えるかどうかを確認するためのビューポートとして使用される要素です。指定されなかった場合、または null の場合は既定でブラウザーのビューポートが使用されます。

rootMargin:
root の周りのマージンです。 CSS の margin プロパティに似た値を指定することができます。例えば、"10px 20px 30px 40px" (top, right, bottom, left) のようなものです。この値はパーセント値にすることができます。この一連の値は、交差を計算する前にルート要素の範囲のボックスの各辺を拡大または縮小させることができます。既定値はすべてゼロです。

rootMarginを指定することによって交差判定位置の調整を行うことができます。

threshold:
単一の数値または数値の配列で、ターゲットがどのくらいの割合で見えている場合にオブザーバーのコールバックを実行するかを示します。見える範囲が 50% を超えたときのみ検出する場合は値 0.5 を使用します。 25% を超える度にコールバックを実行する場合は、 [0, 0.25, 0.5, 0.75, 1] という配列を指定します。既定値は 0 です(つまり、 1 ピクセルでも表示されるとコールバックが実行されます)。 1.0 の値は全てのピクセルが見えるようになるまで、閾値を超えたとはみなされないことを意味します。

thresholdを指定することによって、交差したパーセンテージごとにコールバックの実行を行う事ができます。

また、if(entries[0].isIntersecting)の箇所でIntersectionObserverEntryというインスタンを参照しております。
IntersectionObserverEntryを参照する際の注意点なのですが、インスタンスは配列でのみ取得する形になっています。これはページと同じ高さに要素が並んでいた際に複数要素を取得する場合があるからです。
そして、indexを指定してisIntersectingプロパティを参照する事により、指定した要素が画面と交差しているか(画面内に入っているか)の判定を参照する事ができます。

上記が実際に動作確認を行なった結果になります。
上部に配置されたヘッダーがスクロールによって隠れてしまってもページ下部に表示されて追従させる事ができました!
アニメーションなんかもつけるともっと良い感じのUIになりそうですね!

参考記事:

IntersectionObserver、使い方(理解編)。
https://fuuno.net/ani/ani54/ani54.html

JavaScriptのIntersection Observerでスクロールに合わせてグラデーションの色を変更する
https://www.webcreatorbox.com/tech/intersection-observer

Arsaga Developers Blog

Discussion