zoukankan      html  css  js  c++  java
  • javascript 常用手势 分析

    javascript 常用手势, 个人觉得有3个 tap,swipe(swipeLeft,swipeRight,swipeTop,swipeRight),hold

    tap 是轻击 判断的原则是,在toustart后,移动范围不超过10px(圆的范围),就算是 轻击了

    swipe 是轻滑(轻扫) 判断在toustart后,时间间隔小于300ms,移动范围大于20,就判断是轻滑

    hold(常按) 按住 移动范围小于10px,时间大于200ms,就认为他是hold


    自定义手势,网上相关的源码很多,我也找了一个来研究,叫touch.js,挺不错的支持pc端,移动端(移动端就是touchstart,pc端就是mousedown),虽然有些小bug,比如事件删除有问题

    touch.js的地址 http://touch.code.baidu.com/


    一些要准备的基础

    手势的基本实现原理

    阉割源码解析

    支持移动端 pc端的阉割源码解析

    zepto的手势源码解析

    一些我遇到的手势问题


    一些要准备的基础

    1.对touch相关的东西要了解

    指尖上的js是很好的东西呀

    指尖上的js一

    指尖上的js二

    指尖上的js三


    2.自定义事件CustomEvent

    dom是添加自定义事件的,也可以触发它,它还以冒泡

    火狐的一个官方说明 官方说明

    一篇比较详细的介绍,还有例子 点点点

    自定义事件是可以用chrome看到的,如图

    手势的基本实现原理

    tap,hold,swipe都是js没有的事件,都是由,touchstart,touchmove,touchend touchcancel这些事件组合而成的

    实现原理就是通过绑定document的”touchstart touchmove touchend touchcancel“事件

    当touch到元素,查看手势是否符合tap,swipe的原则

    如果符合原则,就触发元素绑定的相关事件


    判断移动了多少位置

    比如我点击了元素a,我就就得记下点击时的位置,计算方式如下

    touches[0].pageX,touches[0].pageY

    这个是手指点击的位置离页面顶端的位置(或者是页面的左边)

    然后再touchmove时记下相关的位置

    在touchend或者touchcancel时,用2个数据,算一下移动了多少就行了

    ps:touchend和touchcancel是没有event的所以必须在touchmove里面记录位置

    阉割源码解析

    touch.js 以我的水平来看,并不能很流畅的阅读源码...

    而且有些手势也不是很常用,做了些阉割,写了些注释,方便理解

    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
        <title>wo ca!~</title>
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="format-detection" content="telephone=no">
    </head>
    <style>
        .xx{width: 200px;background: #ccc; height: 100px;}
    </style>
    <body>
    <div id="vv" class="xx"></div>
    <br>
    <div id="ss" class="xx"></div>
    <br>
    <div id="ss1" class="xx">1</div>
    <br>
    <div id="ss2" class="xx">1</div>
    <br>
    <div id="ss3" class="xx"></div>
    <br>
    <div id="ss4" class="xx"></div>
    
    <script>
        (function(){
            var utils = {};
            //获取元素的点击位置
            utils.getPosOfEvent = function(ev){
                var posi = [];
                var src = null;
                for (var t = 0, len = ev.touches.length; t < len; t++) {
                    src = ev.touches[t];
                    posi.push({
                        x: src.pageX,
                        y: src.pageY
                    });
                }
                return posi;
            }
            utils.getType = function(obj) {
                return Object.prototype.toString.call(obj).match(/s([a-z|A-Z]+)/)[1].toLowerCase();
            };
            //获取点击的手指数量        
            utils.getFingers = function(ev) {
                return ev.touches ? ev.touches.length : 1;
            };
            utils.isTouchMove = function(ev) {
                return ev.type === 'touchmove';
            };
            //是否已经结束了手势
            utils.isTouchEnd = function(ev) {
                return (ev.type === 'touchend' || ev.type === 'touchcancel');
            };
            //算2点之间的距离        
            utils.getDistance = function(pos1, pos2) {
                var x = pos2.x - pos1.x,
                    y = pos2.y - pos1.y;
                return Math.sqrt((x * x) + (y * y));
            };
            //算角度
            utils.getAngle = function(p1, p2) {
                return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
            };
            //根据角度 返回up down left right
            utils.getDirectionFromAngle = function(agl) {
                var directions = {
                    up: agl < -45 && agl > -135,
                    down: agl >= 45 && agl < 135,
                    left: agl >= 135 || agl <= -135,
                    right: agl >= -45 && agl <= 45
                };
                for (var key in directions) {
                    if (directions[key]) return key;
                }
                return null;
            };
            utils.reset = function() {
                startEvent = moveEvent = endEvent = null;
                __tapped = __touchStart = startSwiping = false;
                pos = {start: null,move: null,end: null};
            };
    
            //ua
            utils.env = (function() {
                var os = {}, ua = navigator.userAgent,
                    android = ua.match(/(Android)[s/]+([d.]+)/),
                    ios = ua.match(/(iPad|iPhone|iPod)s+OSs([d_.]+)/),
                    wp = ua.match(/(Windowss+Phone)s([d.]+)/),
                    isWebkit = /WebKit/[d.]+/i.test(ua),
                    isSafari = ios ? (navigator.standalone ? isWebkit : (/Safari/i.test(ua) && !/CriOS/i.test(ua) && !/MQQBrowser/i.test(ua))) : false;
                if (android) {
                    os.android = true;
                    os.version = android[2];
                }
                if (ios) {
                    os.ios = true;
                    os.version = ios[2].replace(/_/g, '.');
                    os.ios7 = /^7/.test(os.version);
                    if (ios[1] === 'iPad') {
                        os.ipad = true;
                    } else if (ios[1] === 'iPhone') {
                        os.iphone = true;
                        os.iphone5 = screen.height == 568;
                    } else if (ios[1] === 'iPod') {
                        os.ipod = true;
                    }
                }
                if (isWebkit) {
                    os.webkit = true;
                }
                if (isSafari) {
                    os.safari = true;
                }
                return os;
            })();        
    
            //已配置  tap hold swipe表示是否开启手势
            //tapTime tap事件延迟触发的时间
            //holdTime  hold事件多少秒后触发
            //tapMaxDistance 触发tap的时候 最小的移动范围
            //swipeMinDistance 触发swipe的时候 最小的移动范围
            //swipeTime  touchstart 到touchend之前的时间 如果小于swipeTime  才会触发swipe手势
            var config = {
                tap: true,
                tapMaxDistance: 10,
                hold: true,
                tapTime: 200,
                holdTime: 650,
                swipe: true,
                swipeTime: 300,
                swipeMinDistance: 18
            };
            var smrEventList = {
                TOUCH_START: 'touchstart',
                TOUCH_MOVE: 'touchmove',
                TOUCH_END: 'touchend',
                TOUCH_CANCEL: 'touchcancel',
                SWIPE_START: 'swipestart',
                SWIPING: 'swiping',
                SWIPE_END: 'swipeend',
                SWIPE_LEFT: 'swipeleft',
                SWIPE_RIGHT: 'swiperight',
                SWIPE_UP: 'swipeup',
                SWIPE_DOWN: 'swipedown',
                SWIPE: 'swipe',
                HOLD: 'hold',
                TAP: 'tap',
            };
            /** 手势识别 */
            //记录 开始 移动 结束时候的位置
            var pos = {
                start: null,
                move: null,
                end: null
            };
            var __touchStart = true;
            var __tapped;
            var __prev_tapped_end_time;
            var __prev_tapped_pos;
            var __holdTimer = null;
            var startTime;
            var startEvent;
            var moveEvent;
            var endEvent;
            var startSwiping;
    
    
            var gestures = {
                swipe: function(ev) {
                    var el = ev.target;
                    if (!__touchStart || !pos.move || utils.getFingers(ev) > 1) {
                        return;
                    }
                       //计算 时间  距离  角度
                    var now = Date.now();
                    var touchTime = now - startTime;
                    var distance = utils.getDistance(pos.start[0], pos.move[0]);
                    var angle = utils.getAngle(pos.start[0], pos.move[0]);
                    var direction = utils.getDirectionFromAngle(angle);
                    var touchSecond = touchTime / 1000;
                    var eventObj = {
                        type: smrEventList.SWIPE,
                        originEvent: ev,
                        direction: direction,
                        distance: distance,
                        distanceX: pos.move[0].x - pos.start[0].x,
                        distanceY: pos.move[0].y - pos.start[0].y,
                        x: pos.move[0].x - pos.start[0].x,
                        y: pos.move[0].y - pos.start[0].y,
                        angle: angle,
                        duration: touchTime,
                        fingersCount: utils.getFingers(ev)
                    };
                    if (config.swipe) {
                        var swipeTo = function() {
                            var elt = smrEventList;
                            switch (direction) {
                                case 'up':
                                    engine.trigger(el, elt.SWIPE_UP, eventObj);
                                    break;
                                case 'down':
                                    engine.trigger(el, elt.SWIPE_DOWN, eventObj);
                                    break;
                                case 'left':
                                    engine.trigger(el, elt.SWIPE_LEFT, eventObj);
                                    break;
                                case 'right':
                                    engine.trigger(el, elt.SWIPE_RIGHT, eventObj);
                                    break;
                            }
                        };
                        if (!startSwiping) {
                            eventObj.fingerStatus = eventObj.swipe = 'start';
                            //大于tap的最小距离 才算进入swipe手势
                            if(distance>config.tapMaxDistance){
                                startSwiping = true;
                            }                        
                        } else if (utils.isTouchMove(ev)) {
                            eventObj.fingerStatus = eventObj.swipe = 'move';
                            engine.trigger(el, smrEventList.SWIPING, eventObj);
                        } else if (utils.isTouchEnd(ev)) {
                            eventObj.fingerStatus = eventObj.swipe = 'end';
                            //事件要短  距离要有点远
                            if (config.swipeTime > touchTime && distance > config.swipeMinDistance) {
                                swipeTo();
                                engine.trigger(el, smrEventList.SWIPE, eventObj, false);
                            }
                        }
                    }
                },
                tap : function(ev){
                    var el = ev.target;
                    //如果设置了tap为true  才会触发该手势
                    if (config.tap) {
                        var now = Date.now();
                        var touchTime = now - startTime;
                        var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
                        clearTimeout(__holdTimer);
                        //如果移动的距离比设置的距离大(10)  就不算是tap    
                        if (config.tapMaxDistance < distance) return;
    
                        __tapped = true;
                        __prev_tapped_end_time = now;
                        __prev_tapped_pos = pos.start[0];
                        __tapTimer = setTimeout(function() {
                                engine.trigger(el, smrEventList.TAP, {
                                    type: smrEventList.TAP,
                                    originEvent: ev
                                });
                            },
                            config.tapTime);
                    }
                },
                hold: function(ev) {
                    var el = ev.target;
                    //如果设置了hold为true  才会触发该手势
                    if (config.hold) {
                        clearTimeout(__holdTimer);
                        __holdTimer = setTimeout(function() {
                                if (!pos.start) return;
                                var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
                                //如果移动的距离大于配置的距离(10)  就不触发hold
                                if (config.tapMaxDistance < distance) return;
    
                                if (!__tapped) {
                                    engine.trigger(el, "hold", {
                                        type: 'hold',
                                        originEvent: ev,
                                        fingersCount: utils.getFingers(ev),
                                        position: pos.start[0]
                                    });
                                }
                            },
                            config.holdTime);
                    }
                }
            }
    
            /** 底层事件绑定/代理支持  */
            var engine = {
                proxyid: 0,
                proxies: [],
                trigger : function(el, evt, detail){
                    detail = detail || {};
                    var e, opt = {
                            bubbles: true,
                            cancelable: true,
                            detail: detail
                        };
                    try {
                        //这里是触发 自定义事件
                        if (typeof CustomEvent !== 'undefined') {
                            e = new CustomEvent(evt, opt);
                            if (el) {
                                el.dispatchEvent(e);
                            }
                        } else {
                            e = document.createEvent("CustomEvent");
                            e.initCustomEvent(evt, true, true, detail);
                            if (el) {
                                el.dispatchEvent(e);
                            }
                        }
                    } catch (ex) {
                        console.warn("Touch.js is not supported by environment.");
                    }
                },
                bind: function(el, evt, handler) {
                    el.listeners = el.listeners || {};
                    //proxy才是真正元素绑定的事件
                    var proxy = function(e) {
                        //对ios7的一个兼容  也不知道是什么原理
    
                        if (utils.env.ios7) {
                            utils.forceReflow();
                        }
    
                        e.originEvent = e;
                        for (var p in e.detail) {
                            if (p !== 'type') {
                                e[p] = e.detail[p];
                            }
                        }
                        var returnValue = handler.call(e.target, e);
                        if (typeof returnValue !== "undefined" && !returnValue) {
                            e.stopPropagation();
                            e.preventDefault();
                        }
                    };
    
                    if (!el.listeners[evt]) {
                        el.listeners[evt] = [proxy];
                    } else {
                        el.listeners[evt].push(proxy);
                    }
    
                    handler.proxy = handler.proxy || {};
                    if (!handler.proxy[evt]) {
                        handler.proxy[evt] = [this.proxyid++];
                    } else {
                        handler.proxy[evt].push(this.proxyid++);
                    }
                    this.proxies.push(proxy);
                    if (el.addEventListener) {
                        el.addEventListener(evt, proxy, false);
                    }
                },
                unbind : function(el, evt){
                    var handlers = el.listeners[evt];
                    if (handlers && handlers.length) {
                        handlers.forEach(function(handler) {
                            el.removeEventListener(evt, handler, false);
                        });
                    }
                }
            }
    
            var _on = function(el,evt,handler) {
                //绑定事件  支持多元素 多事件绑定噢
                var evts = evt.split(" ");
                var els = utils.getType(el) === 'string' ? document.querySelectorAll(el) : [el];
                
                evts.forEach(function(evt) {
                    for(var i=0,len=els.length;i<len;i++){
                        engine.bind(els[i], evt, handler);
                    }
                });            
            };
    
            var _off = function(els,evts,handler) {
                //删除绑定事件  支持多元素 多事件删除绑定噢
                var els = utils.getType(els) === 'string' ? document.querySelectorAll(els) : els;
                els = els.length ? Array.prototype.slice.call(els) : [els];
                els.forEach(function(el) {
                    evts = evts.split(" ");
                    evts.forEach(function(evt) {
                        engine.unbind(el, evt, handler);
                    });
                });
                return;
            };        
    
            //这个函数很重要
            // doucment的触屏事件全部在这个里面
            var handlerOriginEvent = function(ev) {
                var el = ev.target;
                switch (ev.type) {
                    case 'touchstart':
                        //记录下刚开始点击的事件和位置
                        __touchStart = true;
                        if (!pos.start || pos.start.length < 2) {
                            pos.start = utils.getPosOfEvent(ev);
                        }
                        startTime = Date.now();
                        startEvent = ev;
                        gestures.hold(ev);                
                        break;
                    case 'touchmove':
                        if (!__touchStart || !pos.start) return;
                        //记录滑动过程中的位置
                        pos.move = utils.getPosOfEvent(ev);
                        gestures.swipe(ev);
                        break;
                    case 'touchend':
                    case 'touchcancel':
                        if (!__touchStart) return;
                        endEvent = ev;
                        //.......
                        if (startSwiping) {
                            gestures.swipe(ev);
                        } else {
                            gestures.tap(ev);
                        }                    
    
                        utils.reset();
                        if (ev.touches && ev.touches.length === 1) {
                            __touchStart = false;
                        }
                        break;                
                }
            }
    
            var init = function(){
                //给 document 绑定 下面这些事件
                var touchEvents = 'touchstart touchmove touchend touchcancel';
                touchEvents.split(" ").forEach(function(evt) {
                    document.addEventListener(evt, handlerOriginEvent, false);
                });
            }
            init();
            window.touch = {
                on  : _on,
                off : _off
            };
        })();
    
    
        touch.on("#vv","tap",function(){
            ss1.innerHTML = ~~ss1.innerHTML+1;
        });
    
        touch.on("#vv","swipeleft",function(){
            ss2.innerHTML = ~~ss2.innerHTML+1;
        });    
        touch.on("#vv","swiperight",function(){
            ss2.innerHTML = ~~ss2.innerHTML-1;
        });
     
        touch.on("#vv","hold",function(){
            ss.innerHTML = ~~ss.innerHTML+1;
        });
    </script>    
    </body>
    </html>

    支持移动端 pc端的阉割源码解析

    taobao的页面在ipad上,图片的轮询是支持手势滑动的,天涯的也一样,在pc就支持click了,所以手势封装也是支持pc和移动端才好

    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
        <title>wo ca!~</title>
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="format-detection" content="telephone=no">
    </head>
    <style>
        .xx{width: 200px;background: #ccc; height: 100px;}
    </style>
    <body>
    <div id="vv" class="xx"></div>
    <br>
    <div id="ss" class="xx"></div>
    <br>
    <div id="ss1" class="xx">1</div>
    <br>
    <div id="ss2" class="xx">1</div>
    <br>
    <div id="ss3" class="xx"></div>
    <br>
    <div id="ss4" class="xx"></div>
    
    <script>
        (function(){
            var utils = {};
            //获取元素的点击位置
            utils.getPosOfEvent = function(ev){
                if (this.hasTouch) {
                    var posi = [];
                    var src = null;
    
                    for (var t = 0, len = ev.touches.length; t < len; t++) {
                        src = ev.touches[t];
                        posi.push({
                            x: src.pageX,
                            y: src.pageY
                        });
                    }
                    return posi;
                } else {
                    return [{
                        x: ev.pageX,
                        y: ev.pageY
                    }];
                }
            }
            utils.hasTouch = ('ontouchstart' in window);
            utils.PCevts = {
                'touchstart': 'mousedown',
                'touchmove': 'mousemove',
                'touchend': 'mouseup',
                'touchcancel': 'mouseout'
            };
            utils.getPCevts = function(evt) {
                return this.PCevts[evt] || evt;
            };        
            utils.getType = function(obj) {
                return Object.prototype.toString.call(obj).match(/s([a-z|A-Z]+)/)[1].toLowerCase();
            };
            //获取点击的手指数量        
            utils.getFingers = function(ev) {
                return ev.touches ? ev.touches.length : 1;
            };
            utils.isTouchMove = function(ev) {
                return (ev.type === 'touchmove' || ev.type === 'mousemove');
            };
            //是否已经结束了手势
            utils.isTouchEnd = function(ev) {
                return (ev.type === 'touchend' || ev.type === 'mouseup' || ev.type === 'touchcancel');
            };
            //算2点之间的距离        
            utils.getDistance = function(pos1, pos2) {
                var x = pos2.x - pos1.x,
                    y = pos2.y - pos1.y;
                return Math.sqrt((x * x) + (y * y));
            };
            //算角度
            utils.getAngle = function(p1, p2) {
                return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
            };
            //根据角度 返回up down left right
            utils.getDirectionFromAngle = function(agl) {
                var directions = {
                    up: agl < -45 && agl > -135,
                    down: agl >= 45 && agl < 135,
                    left: agl >= 135 || agl <= -135,
                    right: agl >= -45 && agl <= 45
                };
                for (var key in directions) {
                    if (directions[key]) return key;
                }
                return null;
            };
            utils.reset = function() {
                startEvent = moveEvent = endEvent = null;
                __tapped = __touchStart = startSwiping = false;
                pos = {start: null,move: null,end: null};
            };
    
            //ua
            utils.env = (function() {
                var os = {}, ua = navigator.userAgent,
                    android = ua.match(/(Android)[s/]+([d.]+)/),
                    ios = ua.match(/(iPad|iPhone|iPod)s+OSs([d_.]+)/),
                    wp = ua.match(/(Windowss+Phone)s([d.]+)/),
                    isWebkit = /WebKit/[d.]+/i.test(ua),
                    isSafari = ios ? (navigator.standalone ? isWebkit : (/Safari/i.test(ua) && !/CriOS/i.test(ua) && !/MQQBrowser/i.test(ua))) : false;
                if (android) {
                    os.android = true;
                    os.version = android[2];
                }
                if (ios) {
                    os.ios = true;
                    os.version = ios[2].replace(/_/g, '.');
                    os.ios7 = /^7/.test(os.version);
                    if (ios[1] === 'iPad') {
                        os.ipad = true;
                    } else if (ios[1] === 'iPhone') {
                        os.iphone = true;
                        os.iphone5 = screen.height == 568;
                    } else if (ios[1] === 'iPod') {
                        os.ipod = true;
                    }
                }
                if (isWebkit) {
                    os.webkit = true;
                }
                if (isSafari) {
                    os.safari = true;
                }
                return os;
            })();        
    
            //已配置  tap hold swipe表示是否开启手势
            //tapTime tap事件延迟触发的时间
            //holdTime  hold事件多少秒后触发
            //tapMaxDistance 触发tap的时候 最小的移动范围
            //swipeMinDistance 触发swipe的时候 最小的移动范围
            //swipeTime  touchstart 到touchend之前的时间 如果小于swipeTime  才会触发swipe手势
            var config = {
                tap: true,
                tapMaxDistance: 10,
                hold: true,
                tapTime: 200,
                holdTime: 650,
                swipe: true,
                swipeTime: 300,
                swipeMinDistance: 18
            };
            var smrEventList = {
                TOUCH_START: 'touchstart',
                TOUCH_MOVE: 'touchmove',
                TOUCH_END: 'touchend',
                TOUCH_CANCEL: 'touchcancel',
                MOUSE_DOWN: 'mousedown',
                MOUSE_MOVE: 'mousemove',
                MOUSE_UP: 'mouseup',
                CLICK: 'click',
                PINCH_START: 'pinchstart',
                PINCH_END: 'pinchend',
                PINCH: 'pinch',
                PINCH_IN: 'pinchin',
                PINCH_OUT: 'pinchout',
                ROTATION_LEFT: 'rotateleft',
                ROTATION_RIGHT: 'rotateright',
                ROTATION: 'rotate',
                SWIPE_START: 'swipestart',
                SWIPING: 'swiping',
                SWIPE_END: 'swipeend',
                SWIPE_LEFT: 'swipeleft',
                SWIPE_RIGHT: 'swiperight',
                SWIPE_UP: 'swipeup',
                SWIPE_DOWN: 'swipedown',
                SWIPE: 'swipe',
                DRAG: 'drag',
                DRAGSTART: 'dragstart',
                DRAGEND: 'dragend',
                HOLD: 'hold',
                TAP: 'tap',
                DOUBLE_TAP: 'doubletap'
            };
            /** 手势识别 */
            //记录 开始 移动 结束时候的位置
            var pos = {
                start: null,
                move: null,
                end: null
            };
            var __touchStart = false;
            var __tapped;
            var __prev_tapped_end_time;
            var __prev_tapped_pos;
            var __holdTimer = null;
            var startTime=0;
            var startEvent;
            var moveEvent;
            var endEvent;
            var startSwiping;
    
    
            var gestures = {
                swipe: function(ev) {
                    var el = ev.target;
    
                    if (!__touchStart || !pos.move || utils.getFingers(ev) > 1) {
                        return;
                    }
    
                       //计算 时间  距离  角度
                    var now = Date.now();
                    var touchTime = now - startTime;
                    var distance = utils.getDistance(pos.start[0], pos.move[0]);
                    var angle = utils.getAngle(pos.start[0], pos.move[0]);
                    var direction = utils.getDirectionFromAngle(angle);
                    var touchSecond = touchTime / 1000;
                    var eventObj = {
                        type: smrEventList.SWIPE,
                        originEvent: ev,
                        direction: direction,
                        distance: distance,
                        distanceX: pos.move[0].x - pos.start[0].x,
                        distanceY: pos.move[0].y - pos.start[0].y,
                        x: pos.move[0].x - pos.start[0].x,
                        y: pos.move[0].y - pos.start[0].y,
                        angle: angle,
                        duration: touchTime,
                        fingersCount: utils.getFingers(ev)
                    };
                    if (config.swipe) {
                        var swipeTo = function() {
                            var elt = smrEventList;
                            switch (direction) {
                                case 'up':
                                    engine.trigger(el, elt.SWIPE_UP, eventObj);
                                    break;
                                case 'down':
                                    engine.trigger(el, elt.SWIPE_DOWN, eventObj);
                                    break;
                                case 'left':
                                    engine.trigger(el, elt.SWIPE_LEFT, eventObj);
                                    break;
                                case 'right':
                                    engine.trigger(el, elt.SWIPE_RIGHT, eventObj);
                                    break;
                            }
                        };
                        if (!startSwiping) {
                            eventObj.fingerStatus = eventObj.swipe = 'start';
                            //大于tap的最小距离 才算进入swipe手势
                            if(distance>config.tapMaxDistance){
                                startSwiping = true;
                            }                        
                        } else if (utils.isTouchMove(ev)) {
                            eventObj.fingerStatus = eventObj.swipe = 'move';
                            engine.trigger(el, smrEventList.SWIPING, eventObj);
                        } else if (utils.isTouchEnd(ev)|| ev.type === 'mouseout') {
                            
    
                            eventObj.fingerStatus = eventObj.swipe = 'end';
                            //事件要短  距离要有点远
                            if (config.swipeTime > touchTime && distance > config.swipeMinDistance) {
    
                                swipeTo();
                                engine.trigger(el, smrEventList.SWIPE, eventObj, false);
                            }
                        }
                    }
                },
                tap : function(ev){
                    var el = ev.target;
                    //如果设置了tap为true  才会触发该手势
                    if (config.tap) {
                        var now = Date.now();
                        var touchTime = now - startTime;
                        var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
                        clearTimeout(__holdTimer);
                        //如果移动的距离比设置的距离大(10)  就不算是tap    
                        if (config.tapMaxDistance < distance) return;
    
                        __tapped = true;
                        __prev_tapped_end_time = now;
                        __prev_tapped_pos = pos.start[0];
                        __tapTimer = setTimeout(function() {
                                engine.trigger(el, smrEventList.TAP, {
                                    type: smrEventList.TAP,
                                    originEvent: ev
                                });
                            },
                            config.tapTime);
                    }
                },
                hold: function(ev) {
                    var el = ev.target;
                    //如果设置了hold为true  才会触发该手势
                    if (config.hold) {
                        clearTimeout(__holdTimer);
                        __holdTimer = setTimeout(function() {
                                if (!pos.start) return;
                                var distance = utils.getDistance(pos.start[0], pos.move ? pos.move[0] : pos.start[0]);
                                //如果移动的距离大于配置的距离(10)  就不触发hold
                                if (config.tapMaxDistance < distance) return;
    
                                if (!__tapped) {
                                    engine.trigger(el, "hold", {
                                        type: 'hold',
                                        originEvent: ev,
                                        fingersCount: utils.getFingers(ev),
                                        position: pos.start[0]
                                    });
                                }
                            },
                            config.holdTime);
                    }
                }
            }
    
            /** 底层事件绑定/代理支持  */
            var engine = {
                proxyid: 0,
                proxies: [],
                trigger : function(el, evt, detail){
                    detail = detail || {};
                    var e, opt = {
                            bubbles: true,
                            cancelable: true,
                            detail: detail
                        };
                    try {
                        //这里是触发 自定义事件
                        if (typeof CustomEvent !== 'undefined') {
                            e = new CustomEvent(evt, opt);
                            if (el) {
                                el.dispatchEvent(e);
                            }
                        } else {
                            e = document.createEvent("CustomEvent");
                            e.initCustomEvent(evt, true, true, detail);
                            if (el) {
                                el.dispatchEvent(e);
                            }
                        }
                    } catch (ex) {
                        console.warn("Touch.js is not supported by environment.");
                    }
                },
                bind: function(el, evt, handler) {
                    el.listeners = el.listeners || {};
                    //proxy才是真正元素绑定的事件
                    var proxy = function(e) {
                        //对ios7的一个兼容  也不知道是什么原理
    
                        if (utils.env.ios7) {
                            utils.forceReflow();
                        }
    
                        e.originEvent = e;
                        for (var p in e.detail) {
                            if (p !== 'type') {
                                e[p] = e.detail[p];
                            }
                        }
                        var returnValue = handler.call(e.target, e);
                        if (typeof returnValue !== "undefined" && !returnValue) {
                            e.stopPropagation();
                            e.preventDefault();
                        }
                    };
    
                    if (!el.listeners[evt]) {
                        el.listeners[evt] = [proxy];
                    } else {
                        el.listeners[evt].push(proxy);
                    }
    
                    handler.proxy = handler.proxy || {};
                    if (!handler.proxy[evt]) {
                        handler.proxy[evt] = [this.proxyid++];
                    } else {
                        handler.proxy[evt].push(this.proxyid++);
                    }
                    this.proxies.push(proxy);
                    if (el.addEventListener) {
                        el.addEventListener(evt, proxy, false);
                    }
                },
                unbind : function(el, evt){
                    var handlers = el.listeners[evt];
                    if (handlers && handlers.length) {
                        handlers.forEach(function(handler) {
                            el.removeEventListener(evt, handler, false);
                        });
                    }
                }
            }
    
            var _on = function(el,evt,handler) {
                //绑定事件  支持多元素 多事件绑定噢
                var evts = evt.split(" ");
                var els = utils.getType(el) === 'string' ? document.querySelectorAll(el) : [el];
                
                evts.forEach(function(evt) {
                    if (!utils.hasTouch) {
                        evt = utils.getPCevts(evt);
                    }                
                    for(var i=0,len=els.length;i<len;i++){
                        engine.bind(els[i], evt, handler);
                    }
                });            
            };
    
            var _off = function(els,evts,handler) {
                //删除绑定事件  支持多元素 多事件删除绑定噢
                var els = utils.getType(els) === 'string' ? document.querySelectorAll(els) : els;
                els = els.length ? Array.prototype.slice.call(els) : [els];
                els.forEach(function(el) {
                    evts = evts.split(" ");
                    evts.forEach(function(evt) {
                        if (!utils.hasTouch) {
                            evt = utils.getPCevts(evt);
                        }                    
                        engine.unbind(el, evt, handler);
                    });
                });
                return;
            };        
    
            //这个函数很重要
            // doucment的触屏事件全部在这个里面
            var handlerOriginEvent = function(ev) {
                var el = ev.target;
    
                switch (ev.type) {
                    case 'mousedown':
                    case 'touchstart':
                        //记录下刚开始点击的事件和位置
                        __touchStart = true;
                        if (!pos.start || pos.start.length < 2) {
                            pos.start = utils.getPosOfEvent(ev);
                        }
                        startTime = Date.now();
                        startEvent = ev;
                        gestures.hold(ev);                
                        break;
                    case 'touchmove':
                    case 'mousemove':
                        if (!__touchStart || !pos.start) return;
                        //记录滑动过程中的位置
                        pos.move = utils.getPosOfEvent(ev);
                        gestures.swipe(ev);
                        break;
                    case 'touchend':
                    case 'touchcancel':
                    case 'mouseup':
                    case 'moudeout':                
                        if (!__touchStart) return;
                        endEvent = ev;
                        //.......
                        if (startSwiping) {
                            gestures.swipe(ev);
                        } else {
                            gestures.tap(ev);
                        }                    
    
                        utils.reset();
                        if (ev.touches && ev.touches.length === 1) {
                            __touchStart = false;
                        }
                        break;                
                }
            }
    
            var init = function(){
                //给 document 绑定 下面这些事件
                var mouseEvents = 'mouseup mousedown mousemove',
                    touchEvents = 'touchstart touchmove touchend touchcancel';
                var bindingEvents = utils.hasTouch ? touchEvents : mouseEvents;
    
                bindingEvents.split(" ").forEach(function(evt) {
                    document.addEventListener(evt, handlerOriginEvent, false);
                });
            }
            init();
            window.touch = {
                on  : _on,
                off : _off
            };
        })();
    
        touch.on("#vv","tap",function(){
            ss1.innerHTML = ~~ss1.innerHTML+1;
        });
    
        touch.on("#vv","swipeleft",function(){
            ss2.innerHTML = ~~ss2.innerHTML+1;
        });    
        touch.on("#vv","swiperight",function(){
            ss2.innerHTML = ~~ss2.innerHTML-1;
        });
     
        touch.on("#vv","hold",function(){
            ss.innerHTML = ~~ss.innerHTML+1;
        });
    </script>    
    </body>
    </html>

    zepto的手势源码解析

    zepto自己以移动端的jq自喻,然后提供了一套移动端的手势

    touch的下载地址 https://github.com/madrobby/zepto/blob/master/src/touch.js#files

    这个touch的好处就是可以支持jq的事件绑定方式,比如$("#xx").bind("tap",fun),容易理解,容易上手

    这个touch的实现方式和百度的touch实现基本是一样的,document去绑定touchstart,touchmove,touchend,touchcancel然后经过一些列的判断

    去掉ms的兼容 , 去掉一些手势后的代码    全部代码下载地址    https://github.com/madrobby/zepto/blob/master/src/touch.js#files

    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
        <title>wo ca!~</title>
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="format-detection" content="telephone=no">
    </head>
    <style>
        .xx{width: 200px;background: #ccc; height: 100px;}
    </style>
    <body>
    <div id="vv" class="xx a"></div>
    <br>
    <div id="ss" class="xx a">1</div>
    <br>
    <div id="ss1" class="xx">1</div>
    <br>
    <div id="ss2" class="xx">1</div>
    <br>
    
    
    <script src="http://static.paipaiimg.com/paipai_h5/js/ttj/zepto.min.js"></script>
    <script >
    //     Zepto.js
    //     (c) 2010-2015 Thomas Fuchs
    //     Zepto.js may be freely distributed under the MIT license.
    
    ;(function($){
      var touch = {},
        touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
        longTapDelay = 750,
        gesture
    
      function swipeDirection(x1, x2, y1, y2) {
        return Math.abs(x1 - x2) >=
          Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
      }
    
      function longTap() {
        longTapTimeout = null
        if (touch.last) {
          touch.el.trigger('longTap')
          touch = {}
        }
      }
    
      function cancelLongTap() {
        if (longTapTimeout) clearTimeout(longTapTimeout)
        longTapTimeout = null
      }
    
      function cancelAll() {
        if (touchTimeout) clearTimeout(touchTimeout)
        if (tapTimeout) clearTimeout(tapTimeout)
        if (swipeTimeout) clearTimeout(swipeTimeout)
        if (longTapTimeout) clearTimeout(longTapTimeout)
        touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
        touch = {}
      }
    
      function isPrimaryTouch(event){
        return (event.pointerType == 'touch' ||
          event.pointerType == event.MSPOINTER_TYPE_TOUCH)
          && event.isPrimary
      }
    
      $(document).ready(function(){
        var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
    
        $(document)
          .on('touchstart', function(e){
            //取第一个手指的信息
            firstTouch = e.touches[0]
            if (e.touches && e.touches.length === 1 && touch.x2) {
              // Clear out touch movement data if we have it sticking around
              // This can occur if touchcancel doesn't fire due to preventDefault, etc.
              touch.x2 = undefined
              touch.y2 = undefined
            }
            //记录点下的时候
            now = Date.now()
            delta = now - (touch.last || now)
            touch.el = $('tagName' in firstTouch.target ?
              firstTouch.target : firstTouch.target.parentNode)
            touchTimeout && clearTimeout(touchTimeout)
            //记录点下的位置
            touch.x1 = firstTouch.pageX
            touch.y1 = firstTouch.pageY
            touch.last = now
            longTapTimeout = setTimeout(longTap, longTapDelay)
          })
          .on('touchmove', function(e){
            firstTouch = e.touches[0];
            //记录移动到的位置 和移动的距离
            cancelLongTap();
            touch.x2 = firstTouch.pageX
            touch.y2 = firstTouch.pageY
    
            deltaX += Math.abs(touch.x1 - touch.x2)
            deltaY += Math.abs(touch.y1 - touch.y2)
          })
          .on('touchend', function(e){
            cancelLongTap()
            //判断移动的范围来判断是 tap 还是 swipe
            // swipe
            if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
                (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
    
              swipeTimeout = setTimeout(function() {
                touch.el.trigger('swipe')
                touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
                touch = {}
              }, 0)
    
            // normal tap
            else if ('last' in touch)
              // don't fire tap when delta position changed by more than 30 pixels,
              // for instance when moving to a point and back to origin
              if (deltaX < 30 && deltaY < 30) {
                // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
                // ('tap' fires before 'scroll')
                tapTimeout = setTimeout(function() {
                  // trigger universal 'tap' with the option to cancelTouch()
                  // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
                  var event = $.Event('tap')
                  event.cancelTouch = cancelAll
                  touch.el.trigger(event)
                }, 0)
              } else {
                touch = {}
              }
              deltaX = deltaY = 0
    
          })
          // when the browser window loses focus,
          // for example when a modal dialog is shown,
          // cancel all ongoing events
          .on('touchcancel', cancelAll)
    
        // scrolling the window indicates intention of the user
        // to scroll, not tap or swipe, so cancel all ongoing events
        $(window).on('scroll', cancelAll)
      })
    
      ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
        'tap',  'longTap'].forEach(function(eventName){
        $.fn[eventName] = function(callback){
          //给元素绑定上面的事件  
        return this.on(eventName, callback) 
        }
      })
    })(Zepto);    
    </script>
    <script>
        $("#vv").bind("tap",function(){
            ss.innerHTML = ~~ss.innerHTML+1; 
        });
        $("#vv").bind("swipeLeft",function(){
            ss1.innerHTML = ~~ss1.innerHTML+1;
        });    
        $("#vv").bind("longTap",function(){
            ss2.innerHTML = ~~ss2.innerHTML+1;
        });        
    </script>
    </body>
    </html>

    一些我遇到的手势问题

    问题1

    在有些android的版本上 touchend不触发

    在Android 4.0.x的版本上我遇到过,很蛋疼,比如小米1的最开始的版本就遇到过

    如果在touchmove中加上 阻止默认行为 是可以的(e.preventDefault();),但是会带来另为一个严重的问题,就是无法向下滑动,真实无解的问题,好在这个版本已经离我们远去

    这个问题的一些讨论

    https://code.google.com/p/android/issues/detail?id=19827

    http://stackoverflow.com/questions/7691551/touchend-event-in-ios-webkit-not-firing


    问题2

    透传的问题

    透传应该分2中,

    一种是上面的div隐藏,触发到下面的元素的click,都用tap就可解决,不要一个tap一个click 这样不好

    另外一种是上层的元素隐藏,触发到下面input的聚焦,弹出键盘(最常见的场景,就是弹出个这招层,点关闭遮罩层的时候,下面有一个input)

    这个问题都找不到好的解决方案,我在项目中的做法是有一个透明的遮罩层,先关闭遮罩层,在等280ms关闭这个透明的遮罩层

  • 相关阅读:
    巨蟒django之权限9:前端展示修改删除合并&&权限展示
    巨蟒django之权限8:排序&&菜单展开权限归属
    巨蟒django之权限7:动态生成一级&&二级菜单
    巨蟒django之权限6: 权限控制表设计&&登录权限
    巨蟒django之CRM5 学习记录&&课程记录&&班级管理&&私户的数量上限
    巨蟒django之CRM4 一些小功能
    巨蟒django之CRM3 添加和编辑客户&&公户和私户的展示和转换
    巨蟒django之CRM2 展示客户列表&&分页
    巨蟒django之CRM1 需求分析&&表结构设计&&注册登录验证
    sysfs接口函数到建立_DEVICE_ATTR
  • 原文地址:https://www.cnblogs.com/wtcsy/p/touch.html
Copyright © 2011-2022 走看看