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+OS\s([\d_\.]+)/),
                wp = ua.match(/(Windows\s+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+OS\s([\d_\.]+)/),
                wp = ua.match(/(Windows\s+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關閉這個透明的遮罩層


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM