强悍的javascript手势库

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强悍的javascript手势库相关的知识,希望对你有一定的参考价值。

/**
 * Toucher
 * git:https://github.com/cometwo/Toucher-1
 */

"use strict";

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], function () {
            return factory(root);
        });
    } else {
        root.Toucher = factory(root);
    }
}(window, function (root, undefined) {

    if (!"ontouchstart" in window) {
        return;
    }

    var _wrapped;

    //  获取对象上的类名
    function _typeOf(obj) {
        return Object.prototype.toString.call(obj).toLowerCase().slice(8, -1);
    }

    //  获取当前时间戳
    function getTimeStr() {
        return +(new Date());
    }

    //  获取位置信息
    function getPosInfo(ev) {
        var _touches = ev.touches;
        if (!_touches || _touches.length === 0) {
            return;
        }
        return {
            pageX: ev.touches[0].pageX,
            pageY: ev.touches[0].pageY,
            clientX: ev.touches[0].clientX || 0,
            clientY: ev.touches[0].clientY || 0
        };
    }

    //  绑定事件
    function bindEv(el, type, fn) {
        if (el.addEventListener) {
            el.addEventListener(type, fn, false);
        } else {
            el["on" + type] = fn;
        }
    }

    //  解绑事件
    function unBindEv(el, type, fn) {
        if (el.removeEventListener) {
            el.removeEventListener(type, fn, false);
        } else {
            el["on" + type] = fn;
        }
    }

    //  获得滑动方向
    function getDirection(startX, startY, endX, endY) {
        var xRes = startX - endX;
        var xResAbs = Math.abs(startX - endX);
        var yRes = startY - endY;
        var yResAbs = Math.abs(startY - endY);
        var direction = "";

        if (xResAbs >= yResAbs && xResAbs > 25) {
            direction = (xRes > 0) ? "Right" : "Left";
        } else if (yResAbs > xResAbs && yResAbs > 25) {
            direction = (yRes > 0) ? "Down" : "Up";
        }
        return direction;
    }

    //  取得两点之间直线距离
    function getDistance(startX, startY, endX, endY) {
        return Math.sqrt(Math.pow((startX - endX), 2) + Math.pow((startY - endY), 2));
    }

    function getLength(pos) {
        return Math.sqrt(Math.pow(pos.x, 2) + Math.pow(pos.y, 2));
    }

    function cross(v1, v2) {
        return v1.x * v2.y - v2.x * v1.y;
    }

    //  取向量
    function getVector(startX, startY, endX, endY) {
        return (startX * endX) + (startY * endY);
    }

    //  获取角度
    function getAngle(v1, v2) {
        var mr = getLength(v1) * getLength(v2);
        if (mr === 0) {
            return 0
        }
        ;
        var r = getVector(v1.x, v1.y, v2.x, v2.y) / mr;
        if (r > 1) {
            r = 1;
        }
        return Math.acos(r);
    }

    //  获取旋转的角度
    function getRotateAngle(v1, v2) {
        var angle = getAngle(v1, v2);
        if (cross(v1, v2) > 0) {
            angle *= -1;
        }
        return angle * 180 / Math.PI;
    }

    //  包装一个新的事件对象
    function wrapEvent(ev, obj) {
        var res = {
            touches: ev.touches,
            type: ev.type
        };
        if (_typeOf(obj) === "object") {
            for (var i in obj) {
                res[i] = obj[i];
            }
        }
        return res;
    }

    //  把伪数组转换成数组
    function toArray(list) {
        if (list && (typeof list === "object") && isFinite(list.length) && (list.length >= 0) && (list.length === Math.floor(list.length)) && list.length < 4294967296) {
            return [].slice.call(list);
        }
    }

    //  判断一个元素列表里面是否有多个元素
    function isContain(collection, el) {
        if (arguments.length === 2) {
            return collection.some(function (elItem) {
                return el.isEqualNode(elItem);
            });
        }
        return false;
    }

    //  生成一个随机id
    function uId() {
        return Math.random().toString(16).slice(2);
    }

    //  事件模块
    var Event = (function () {

        var storeEvents = {};

        return {

            //  add an event handle
            add: function (type, el, handler) {
                var selector = el,
                    len = arguments.length,
                    finalObject = {}, _type;
                /**
                 * Event.add("swipe", function() {
                 *      //  ...
                 * });
                 */

                if (_typeOf(el) === "string") {
                    el = document.querySelectorAll(el);
                }

                if (len === 2 && _typeOf(el) === "function") {
                    finalObject = {
                        handler: el
                    };
                } else if (len === 3 && el instanceof htmlElement || el instanceof NodeList && _typeOf(handler) === "function") {
                    /**
                     * Event.add("swipe", "#div", function(ev) {
                     *      //  ...
                     * });
                     */
                    _type = _typeOf(el);
                    finalObject = {
                        type: _type,
                        selector: selector,
                        el: _type === "nodelist" ? toArray(el) : el,
                        handler: handler
                    };
                }

                if (!storeEvents[type]) {
                    storeEvents[type] = [];
                }

                storeEvents[type].push(finalObject);
            },

            //  remove an event handle
            remove: function (type, selector) {
                var len = arguments.length;
                if (_typeOf(type) === "string" && _typeOf(storeEvents[type]) === "array" && storeEvents[type].length) {
                    if (len === 1) {
                        storeEvents[type] = [];
                    } else if (len === 2) {
                        storeEvents[type] = storeEvents[type].filter(function (item) {
                            return !(item.selector === selector || _typeOf(selector) !== "string" && item.selector.isEqualNode(selector));
                        });
                    }
                }
            },

            //  trigger an event handle
            trigger: function (type, el, argument) {
                var len = arguments.length;

                /**
                 * Event.trigger("swipe", document.querySelector("#div"), {
                 *      //  ...
                 * });
                 */
                if (len === 3 && _typeOf(storeEvents[type]) === "array" && storeEvents[type].length) {
                    storeEvents[type].forEach(function (item) {
                        if (_typeOf(item.handler) === "function") {
                            if (item.type && item.el) {
                                argument.target = el;
                                if (item.type === "nodelist" && isContain(item.el, el)) {
                                    item.handler(argument);
                                } else if (item.el.isEqualNode && item.el.isEqualNode(el)) {
                                    item.handler(argument);
                                }
                            } else {
                                item.handler(argument);
                            }
                        }
                    });
                }
            }
        };
    })();

    //  构造函数
    function Toucher(selector) {
        return new Toucher.fn.init(selector);
    }

    Toucher.fn = Toucher.prototype = {

        //  修改原型构造器
        constructor: Toucher,

        //  初始化方法
        init: function (selector) {
            this.el = selector instanceof HTMLElement ? selector :
                _typeOf(selector) === "string" ? document.querySelector(selector) : null;
            if (_typeOf(this.el) === "null") {
                throw new Error("you must specify a particular selector or a particular DOM object");
            }
            this.scale = 1;
            this.pinchStartLen = null;
            this.isDoubleTap = false;
            this.triggedSwipeStart = false;
            this.triggedLongTap = false;
            this.delta = null;
            this.last = null;
            this.now = null;
            this.tapTimeout = null;
            this.singleTapTimeout = null;
            this.longTapTimeout = null;
            this.swipeTimeout = null;
            this.startPos = {};
            this.endPos = {};
            this.preTapPosition = {};

            this.cfg = {
                doubleTapTime: 400,
                longTapTime: 700
            };

            //  绑定4个事件
            bindEv(this.el, "touchstart", this._start.bind(this));
            bindEv(this.el, "touchmove", this._move.bind(this));
            bindEv(this.el, "touchcancel", this._cancel.bind(this));
            bindEv(this.el, "touchend", this._end.bind(this));
            return this;
        },

        //  提供config方法进行配置
        config: function (option) {
            if (_typeOf(option) !== "object") {
                throw new Error("method Toucher.config must pass in an anguments which is an instance of Object, but passed in " + option.toString());
            }
            for (var i in option) {
                this.cfg[i] = option[i];
            }
            return this;
        },

        //  on方法绑定事件
        /**
         * var toucher = Toucher({...});
         *
         * toucher.on("swipe", function(ev) {
         *     //   ...
         * });
         *
         * //   or
         *
         * toucher.on("tap", "#id", function(ev) {
         *     //   ...
         * });
         *
         * support events: singleTap,longTap,swipe,swipeStart,swipeEnd,swipeUp,swipeRight,swipeDown,swipeLeft,pinch,rotate
         *
         */

        on: function (type, el, callback) {
            var len = arguments.length;
            if(len === 2) {
                Event.add(type, el);
            } else {
                Event.add(type, el, callback);
            }
            return this;
        },

        //  off 解除绑定
        /**
         *  var toucher = Toucher({...});
         *  toucher.off(type);
         *
         *  //  or
         *
         *  toucher.off(type, selector);
         */
        off: function (type, selector) {
            Event.remove(type, selector);
            return this;
        },

        //  手指刚触碰到屏幕
        _start: function (ev) {
            if (!ev.touches || ev.touches.length === 0) {
                return;
            }

            var self = this;
            var otherToucher, v,
                preV = this.preV,
                target = ev.target;

            self.now = getTimeStr();
            self.startPos = getPosInfo(ev);
            self.delta = self.now - (self.last || self.now);
            self.triggedSwipeStart = false;
            self.triggedLongTap = false;

            //  快速双击
            if (JSON.stringify(self.preTapPosition).length > 2 && self.delta < self.cfg.doubleTapTime && getDistance(self.preTapPosition.clientX, self.preTapPosition.clientY, self.startPos.clientX, self.startPos.clientY) < 25) {
                self.isDoubleTap = true;
            }

            //  长按定时
            self.longTapTimeout = setTimeout(function () {
                _wrapped = {
                    el: self.el,
                    type: "longTap",
                    timeStr: getTimeStr(),
                    position: self.startPos
                };
                Event.trigger("longTap", target, _wrapped);
                self.triggedLongTap = true;
            }, self.cfg.longTapTime);

            //  多个手指放到屏幕
            if (ev.touches.length > 1) {
                self._cancelLongTap();
                otherToucher = ev.touches[1];
                v = {
                    x: otherToucher.pageX - self.startPos.pageX,
                    y: otherToucher.pageY - self.startPos.pageY
                };
                this.preV = v;
                self.pinchStartLen = getLength(v);
                self.isDoubleTap = false;
            }

            self.last = self.now;
            self.preTapPosition = self.startPos;

            ev.preventDefault();
        },

        //  手指在屏幕上移动
        _move: function (ev) {
            if (!ev.touches || ev.touches.length === 0) {
                return;
            }

            var v, otherToucher;
            var self = this;
            var len = ev.touches.length;
            var posNow = getPosInfo(ev);
            var preV = self.preV;
            var currentX = posNow.pageX;
            var currentY = posNow.pageY;
            var target = ev.target;

            //  手指移动取消长按事件和双击
            self._cancelLongTap();
            self.isDoubleTap = false;

            //  一次按下抬起只触发一次swipeStart
            if (!self.triggedSwipeStart) {
                _wrapped = {
                    el: self.el,
                    type: "swipeStart",
                    timeStr: getTimeStr(),
                    position: posNow
                };
                Event.trigger("swipeStart", target, _wrapped);
                self.triggedSwipeStart = true;
            } else {
                _wrapped = {
                    el: self.el,
                    type: "swipe",
                    timeStr: getTimeStr(),
                    position: posNow
                };
                Event.trigger("swipe", target, _wrapped);
            }

            if (len > 1) {
                otherToucher = ev.touches[1];
                v = {
                    x: otherToucher.pageX - currentX,
                    y: otherToucher.pageY - currentY
                };

                //  缩放
                _wrapped = wrapEvent(ev, {
                    el: self.el,
                    type: "pinch",
                    scale: getLength(v) / this.pinchStartLen,
                    timeStr: getTimeStr(),
                    position: posNow
                });
                Event.trigger("pinch", target, _wrapped);

                //  旋转
                _wrapped = wrapEvent(ev, {
                    el: self.el,
                    type: "rotate",
                    angle: getRotateAngle(v, preV),
                    timeStr: getTimeStr(),
                    position: posNow
                });
                Event.trigger("rotate", target, _wrapped);
                ev.preventDefault();
            }

            self.endPos = posNow;
        },

        //  触碰取消
        _cancel: function (ev) {
            clearTimeout(this.longTapTimeout);
            clearTimeout(this.tapTimeout);
            clearTimeout(this.swipeTimeout);
            clearTimeout(self.singleTapTimeout);
        },

        //  手指从屏幕离开
        _end: function (ev) {
            if (!ev.changedTouches) {
                return;
            }

            //  取消长按
            this._cancelLongTap();

            var self = this;
            var direction = getDirection(self.endPos.clientX, self.endPos.clientY, self.startPos.clientX, self.startPos.clientY);
            var callback, target = ev.target;

            if (direction !== "") {
                self.swipeTimeout = setTimeout(function () {
                    _wrapped = wrapEvent(ev, {
                        el: self.el,
                        type: "swipe",
                        timeStr: getTimeStr(),
                        position: self.endPos
                    });
                    Event.trigger("swipe", target, _wrapped);

                    //  获取具体的swipeXyz方向
                    callback = self["swipe" + direction];
                    _wrapped = wrapEvent(ev, {
                        el: self.el,
                        type: "swipe" + direction,
                        timeStr: getTimeStr(),
                        position: self.endPos
                    });
                    Event.trigger(("swipe" + direction), target, _wrapped);

                    _wrapped = wrapEvent(ev, {
                        el: self.el,
                        type: "swipeEnd",
                        timeStr: getTimeStr(),
                        position: self.endPos
                    });
                    Event.trigger("swipeEnd", target, _wrapped);
                }, 0);
            } else if (!self.triggedLongTap) {
                self.tapTimeout = setTimeout(function () {
                    if (self.isDoubleTap) {
                        _wrapped = wrapEvent(ev, {
                            el: self.el,
                            type: "doubleTap",
                            timeStr: getTimeStr(),
                            position: self.startPos
                        });
                        Event.trigger("doubleTap", target, _wrapped);
                        clearTimeout(self.singleTapTimeout);
                        self.isDoubleTap = false;
                    } else {
                        self.singleTapTimeout = setTimeout(function () {
                            _wrapped = wrapEvent(ev, {
                                el: self.el,
                                type: "singleTap",
                                timeStr: getTimeStr(),
                                position: self.startPos
                            });
                            Event.trigger("singleTap", target, _wrapped);
                        }, 100);
                    }
                }, 0);
            }

            this.startPos = {};
            this.endPos = {};
        },

        //  取消长按定时器
        _cancelLongTap: function () {
            if (_typeOf(this.longTapTimeout) !== "null") {
                clearTimeout(this.longTapTimeout);
            }
        }
    };

    Toucher.fn.init.prototype = Toucher.fn;

    return Toucher;

}));

Toucher

 

移动端手势库

API

Toucher("#node").config(Object).on(Object)

    或者

var toucher = Toucher("#node");
toucher.config(Object);
toucher.on(name, callback);

通过

var toucher = Toucher(css selector);

来构造一个Toucher对象

toucher.config(Object)

来配置相关事件的触发条件

touch.on(name, callbck);

touch.on(name, target, callbck);

来绑定事件

完整示例

//  HTML
<div id="toucher">
    <div id="event"></div>
    <ul>
        <li class="list-item"></li>
        <li class="list-item"></li>
        <li class="list-item"></li>
        <li class="list-item"></li>
        <li class="list-item"></li>
        <li class="list-item"></li>
    </ul>
</div>


//  javascript
var toucher = Toucher("#toucher");

//  回调函数会在任何时候被执行,只要event.target为<div>#toucher或者其子元素
toucher.on("singleTap", function(ev) {
    //  ...
});

//  只有当event.target为<div>#event的时候,才会执行后面的回调函数
toucher.on("singleTap", "#event", function(ev) {
    //  ...
});

//  只有当event.target为<li>.list-item的时候,才会执行后面的回调函数
toucher.on("singleTap", document.querySelector(".list-item"), function(ev) {
    //  ...
});

支持的配置项(config)和事件列表(on)

  • config
属性含义类型
longTapTime 触发longTap事件的时间(毫秒),默认700ms Number
doubleTapTime 在多少毫秒内连续点击两次屏幕,触发doubleTap,默认400ms Number
  • on
两个参数
name属性含义callback类型
singleTap 轻击(单个手指) Fucntion
doubleTap 手指放到屏幕(单个手指) Fucntion
longTap 长按 Fucntion
swipe 手指在屏幕上移动 Fucntion
swipeStart 手指在屏幕上移动(只触发一次) Fucntion
swipeEnd 下滑 Fucntion
swipeUp 左滑 Fucntion
swipeRight 右滑 Fucntion
swipeDown 下滑 Fucntion
swipeLeft 左滑 Fucntion
pinch 缩放 Fucntion
rotate 旋转 Fucntion
三个参数

name和callback和上表一样

target可以为具体的css selector, 也可以为具体的DOM元素(document.querySelector(selector)之类方法获取到的)

  • off

解除之前绑定过的代理事件(之前怎样绑定的就怎样解除)

例子
var toucher = Toucher("#div");

//  绑定
toucher.on("singleTap", function(ev){});

//  解除绑定
toucher.off("sigleTap");

或者

var toucher = Toucher("#div");

//  绑定
toucher.on("singleTap", ".list-item", function(ev){});

//  解除绑定
toucher.off("sigleTap", ".list-item");

在线体验

扫描下方二维码或者手机直接访问https://rwson.github.io/Toucher/

技术分享

以上是关于强悍的javascript手势库的主要内容,如果未能解决你的问题,请参考以下文章

用 JavaScript 实现手势库 — 封装手势库前端组件化

用 JavaScript 实现手势库 — 封装手势库前端组件化

用 JavaScript 实现手势库 — 封装手势库前端组件化

用 JavaScript 实现手势库 — 手势逻辑前端组件化

用 JavaScript 实现手势库 — 手势逻辑前端组件化

用 JavaScript 实现手势库 — 手势动画应用前端组件化