0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSでタップイベントを定義する

Posted at

JSでタップイベントをを定義できるライブラリを作った。

発端

本来、タップイベントなんてものは Web の世界では不要なものであろう。
何故なら、スマホをタップすれば click イベントが発火するからだ。

では、何故こんなライブラリを作ったのかというと…

iOS のブラウザで click イベントが発火しない現象があったらからだ!!!

試行錯誤した結果、 touchstart と touchend は機能していることに気づき、これを利用してタップイベントを独自定義しよう!となった。

ライブラリ

simple_tap_event.js
(function() {
    class TapEvent {
        static #maxDistance = 0;
        static set maxDistance(val) {
            this.#maxDistance = Number(val);
        }
        static get maxDistance() {
            return this.#maxDistance;
        }

        static on(element, listener, maxDistance) {
            if (typeof listener !== "function") {
                return;
            }
            if (hasContext(element, listener)) {
                return;
            }

            let getMaxDistance = undefined;
            if (maxDistance === undefined) {
                getMaxDistance = () => this.#maxDistance;
            }
            else {
                const rtnMaxDistance = Number(maxDistance);
                getMaxDistance = () => rtnMaxDistance;
            }

            const touchMap = new Map();

            const handleTouchStart = e => {
                for (const touch of e.changedTouches) {
                    touchMap.set(touch.identifier, {x: touch.clientX, y: touch.clientY});
                }
            };
            const handleTouchEnd = e => {
                const points = [];
                for (const touch of e.changedTouches) {
                    const prevTouch = touchMap.get(touch.identifier);
                    if (prevTouch === undefined) {
                        continue;
                    }
                    touchMap.delete(touch.identifier);
                    const distanceSquared = (prevTouch.x - touch.clientX) ** 2 + (prevTouch.y - touch.clientY) ** 2;
                    if (distanceSquared <= getMaxDistance() ** 2) {
                        points.push({
                            clientX: touch.clientX,
                            clientY: touch.clientY
                        });
                    }
                }
                if (points.length > 0) {
                    listener.call(element, points, {
                        preventDefault: e.preventDefault.bind(e),
                        stopPropagation: e.stopPropagation.bind(e)
                    });
                }
            };
            const handleTouchCancel = e => {
                for (const touch of e.changedTouches) {
                    touchMap.delete(touch.identifier);
                }
            };

            const context = {
                handleTouchStart, handleTouchEnd, handleTouchCancel
            };
            setContext(element, listener, context);

            element.addEventListener("touchstart", handleTouchStart);
            element.addEventListener("touchend", handleTouchEnd);
            element.addEventListener("touchcancel", handleTouchCancel);
        }

        static off(element, listener) {
            if (listener === undefined) {
                if (!contextMap.has(element)) {
                    return;
                }
                for (const listener of contextMap.get(element).keys()) {
                    TapEvent.off(element, listener);
                }
                return;
            }

            if (!hasContext(element, listener)) {
                return;
            }

            const listenerContextMap = contextMap.get(element);

            const {handleTouchStart, handleTouchEnd, handleTouchCancel} = listenerContextMap.get(listener);
            element.removeEventListener("touchstart", handleTouchStart);
            element.removeEventListener("touchend", handleTouchEnd);
            element.removeEventListener("touchcancel", handleTouchCancel);

            listenerContextMap.delete(listener);
            if (listenerContextMap.size === 0) {
                contextMap.delete(element);
                elementSet.delete(element);
            }
        }

        static destroy() {
            for (const element of elementSet) {
                TapEvent.off(element);
            }
            elementSet.clear();
        }
    }

    const contextMap = new WeakMap();
    const elementSet = new Set();
    function setContext(element, listener, context) {
        if (!contextMap.has(element)) {
            contextMap.set(element, new Map());
            elementSet.add(element);
        }
        contextMap.get(element).set(listener, context);
    }
    function hasContext(element, listener) {
        return contextMap.has(element) && contextMap.get(element).has(listener);
    }

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = TapEvent;
    }
    else if (typeof window !== 'undefined') {
        window.TapEvent = TapEvent;
    }
})();

簡単な使い方

const el = document.querySelector("#hoge");

function handleTap() {
    alert("タップされた!");
}

// タップイベントの登録
TapEvent.on(el, handleTap);

// タップイベントの削除
TapEvent.off(el, handleTap);

// タップイベントの登録(引数の意味)
TapEvent.on(el, (points, e) => {
    // points はタップされた座標のリスト
    alert(`clientX: ${points[0].clientX}, clientY: ${points[0].clientY}`);
    // e は Eventオブジェクト の一部の関数が呼び出せる
    e.preventDefault();
    e.stopPropagation();
});

てな感じ。

詳しくは README を読んで。

終わり。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?