LoginSignup
2
2

More than 3 years have passed since last update.

IntersectionObserverを使ってフローティングボタンをエリアごとに変更する

Last updated at Posted at 2021-04-14

スクロールをして特定の要素エリアに入ったら
フローティングボタンを変更するという実装を行いました。
IntersectionObserverを使用することで効率的に書くことができました。
もっと良い書き方もあるとは思いますが、基本的な書き方で
対応できたので紹介したいと思います。

IntersectionObserverの概要

IntersectionObserverは「基準の要素」と「監視される要素」が交差するときに
callback関数を返してくれるAPIです。

基準要素に対して監視要素が見えている割合=交差と考えるとわかりやすいと思います。

実装方法

let options = {
  root: null,
  rootMargin: '50px',
  threshold: 1.0
};

let observer = new IntersectionObserver(callback, options);

root

基準の要素。nullはviewport(ブラウザ画面)

rootMargin

基準要素となるrootにこのrootMarginの値を足して基準の要素にできる。
上記の例だとroot要素の周囲に50px拡大していることになる。
正の値、負の値を設定でき負の値を設定した場合、基準要素を値分縮める。

注意点としてpxや%といった単位が必ず必要。
rootMargin: "50px 0"のようには設定できずrootMargin:"50px 0px"とする。

threshold

コールバックを実行する交差の閾値。
交差の閾値を0~1の間で指定する。

フローティングボタンをエリアごとに変更する

HTML

    <div class="wrapper">
        <div class="box box1" data-id="A"></div>
        <div class="box box2" data-id="B"></div>
        <div class="box box3" data-id="C"></div>
        <div class="box box4" data-id="D"></div>
        <div class="box box5" data-id="E"></div>

        <div class="float_button">
            <a href="#" id="A" class="page-top is-show">A</a>
            <a href="#" id="B" class="page-top">B</a>
            <a href="#" id="C" class="page-top">C</a>
            <a href="#" id="D" class="page-top">D</a>
            <a href="#" id="E" class="page-top">E</a>
        </div>
    </div>

box1 ~ box5にスクロールしてboxごとにフローティングボタンを変更させるようにしました。
スクロールに応じて変更するフローティングボタンはfloat_buttonの中に入れています。
box1ならid="A"のボタン、box2ならid="B"のボタンが表示されます。

JavaScript

let options = {
    root: null,
    rootMargin: "-85% 0px -15px 0px",
    threshold: 0
}
let observer = new IntersectionObserver(callback, options);

// 監視するboxを取得
let boxes = document.querySelectorAll(".box");
// それぞれのboxを監視する
boxes.forEach(box => {
    observer.observe(box);
});

基準となるroot要素はビューポートつまりブラウザ画面全体を設定。
rootMarginは上下の設定をしています。
下へスクロールするときは次の要素が15%見えたらフローティングボタンを変更、
上へスクロールするときは上の要素が85%見えてからフローティングボタンを変更するため上記のような値に設定しています。
ちなみにrootMargin: "-50% 0px"とすると要素の中心にきたときに切り替わるようにできます。
閾値のthresholdはすぐに切り替えるように0に設定しています。

function callback(entries) {
    entries.forEach(entry => {
        // 監視要素が閾値を超えるとtrueになる
        if (entry.isIntersecting) {
            isShowFloatButton(entry.target);
        }
    });
}

// フローティングボタンを全て取得
let buttons = document.querySelectorAll(".page-top");
// フローティングボタン表示
function isShowFloatButton(target) {
    // targetには「box」の処理
    const targetId = target.dataset.id;
    switch (targetId) {
        case "A":
            hiddenButton();
            document.getElementById("A").classList.add("is-show");
            break;
        case "B":
            hiddenButton();
            document.getElementById("B").classList.add("is-show");
            break;
        case "C":
            hiddenButton();
            document.getElementById("C").classList.add("is-show");
            break;
        case "D":
            hiddenButton();
            document.getElementById("D").classList.add("is-show");
            break;
        case "E":
            hiddenButton();
            document.getElementById("E").classList.add("is-show");
            break;
    }
}

function hiddenButton() {
    buttons.forEach(button => {
        button.classList.remove("is-show");
    });
}

監視している要素のdata属性を取得し、switch文で該当するidを持つフローティングボタンのclassを付け外しすることで表示・非表示を切り替えています。

これでフローティングボタンをエリアごとに変更することができました。

コメントにて上記のコードより良い書き方を教えていただきました!ありがとうございます!!(4/15)
要素の増減によって書き換えが発生しないのでこちらのほうが断然良いですね!!
勉強になりました。ありがとうございます。

function isShowFloatButton(target) {
    // targetには「box」の処理
    const targetId = target.dataset.id;
    const targetElement = document.getElementById(targetId);
    if(targetElement !== null) {
        hiddenButton();
        targetElement.classList.add("is-show");
    }
}

function hiddenButton() {
    buttons.forEach(button => {
        button.classList.remove("is-show");
    });
}

最後に

フローティングボタンをスクロールによって切り替えることになったときスクロールイベントでの実装を初めは考えていましたが、IntersectionObserverを知ることができたおかげでかなり楽にコーディングをすることができました。

今回の例以外にも要素が表示されたらカルーセルを動かす、YouTubeを再生する、アニメーションを開始するなど色々な用途に使えるので今後も使っていきたいと思います。

参考

ICS.media
MDN
Polyfill

2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2