「ページトップへ戻る」ボタンを Intersection Observer API で書き直す

Webサイトの右下によくある「ページトップへ戻る」ボタンを Intersection Observer API で書き直してみたのでメモしておきます。

コード

_returnpagetop.js

/**
 * ページトップへ戻る
 *
 * constructor:
 *
 * @param {string} observeSelectorName - 監視対象要素のセレクタ名 (デフォルト: .l-main)
 * @param {string} targetSelectorName  - クラス付与要素のセレクタ名 (デフォルト: .c-returnPageTop)
 */
class returnPageTop {
    /**
     * constructor
     *
     * @param {string} observeSelectorName - 監視対象要素のセレクタ名 (デフォルト: .l-main)
     * @param {string} targetSelectorName  - クラス付与要素のセレクタ名 (デフォルト: .c-returnPageTop)
     */
    constructor(observeSelectorName = '.l-main', targetSelectorName = '.c-returnPageTop') {
        // 監視対象要素
        this.observeElms = document.querySelectorAll(observeSelectorName);
        // DOM to Array
        this.observeElmsArray = Array.prototype.slice.call(this.observeElms, 0);
        // クラス付与要素
        this.targetElms = document.querySelectorAll(targetSelectorName);
        // クラス付与要素の Array
        this.targetElmsArray = Array.prototype.slice.call(this.targetElms, 0);
        // options
        this.options = {
            root: null,
            rootMargin: '0px 0px -12%',
            threshold: 0
        };
        // ブラウザの高さ
        this.clientHeight = document.documentElement.clientHeight;
        // ページトップへ戻る
        this.rptObserver = this.rptShow(this.clientHeight);
        window.addEventListener('resize', () => {
            // resize でブラウザの表示領域の高さが変動したら
            this.clientHeight = document.documentElement.clientHeight;
            // 一旦監視を止める
            this.rptObserver.disconnect();
            // 再度監視
            this.rptObserver = this.rptShow(this.clientHeight);
        });
    }
    /**
     * IntersectionObserver で指定するコールバック関数。ページトップへ戻るのクリック対象要素を、 targetSelectorName で指定した要素と交差するかを判定して、その結果で表示・非表示を切り替えるための .activeクラス の着脱を行う
     *
     * @param {Array} - elms
     */
    addClass = (elms) => {
        const elmsArray = Array.prototype.slice.call(elms, 0);
        for (const elm of elmsArray) {
            // ブラウザ表示領域に対する対象要素の位置
            const elmRectCoor = elm.target.getBoundingClientRect();
            if ( 0 > elmRectCoor.bottom ) {
                // ブラウザ表示領域に対する対象要素の上端の位置 が ブラウザの表示領域 より上
                for (const targetElm of this.targetElmsArray) {
                    targetElm.classList.add('active');
                }
            }
            else {
                for (const targetElm of this.targetElmsArray) {
                    targetElm.classList.remove('active');
                }
            }
        }
    }
    /**
     * observeSelectorName で指定した複数の要素(ページトップへ戻るボタン)を IntersectionObserverクラス による監視対象とする
     *
     * @returns {Object} observer - IntersectionObserverクラス
     */
    rptShow = () => {
        // instance
        const observer = new IntersectionObserver(this.addClass, this.options);
        // observe
        for (const selector of this.observeElmsArray) {
            observer.observe(selector);
        }
        return observer;
    }
};

module.exports = returnPageTop;

おおよそコメントの通りですが、ベースはIntersection Observer API を試すです。

……1年半弱前のサンプルがようやく「ここで使うだろうな」と思った場所で使うことができました。

また、かつてのサンプルではコールバック関数へ交差したかを判定する対象要素をどうやって渡すか考えあぐねていたのですが、よくよく考えればコンストラクタで宣言してプロパティにしておけば引数として渡さなくても利用できるのでそれで解決しました。おかげでメソッドの中に関数がいるという宜しくない状況は回避できるようになりました。

app.js

import returnPageTop from './_returnpagetop';

// returnPageTop
new returnPageTop();

呼び出し元はシンプルにクラスをインスタンス化するだけ。

Webpack 等のモジュールバンドラ使用を想定しています。

参考

Intersection Observer API

Array.prototype.slice.call(arguments)

getBoundingClientRect

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。