一、前言

這幾個(gè)月事情比較多,寫(xiě)了一些博客都沒(méi)有來(lái)得及整理發(fā)布,今天剛好有一位同事在開(kāi)發(fā)前端頁(yè)面的時(shí)候用到了手勢(shì)判斷。所以翻出了之前寫(xiě)的 demo,順便整理一下作為記錄。

手勢(shì)判斷在各種應(yīng)用中都十分常見(jiàn),如 APP 中的手勢(shì)翻頁(yè),前進(jìn)后退等等,如微博做得就特別好,微信的話就不想吐槽了。不扯太遠(yuǎn),H5 開(kāi)發(fā)中手勢(shì)判斷一般多用于一些交互比較靈活的場(chǎng)景,例如大轉(zhuǎn)盤抽獎(jiǎng)游戲,旋轉(zhuǎn)菜單,酷跑,打磚塊游戲等等。今天不具體到這些小游戲的開(kāi)發(fā),我們重點(diǎn)講講實(shí)現(xiàn)的原理。其實(shí)比較基礎(chǔ),大神請(qǐng)自動(dòng)忽略。

二、實(shí)現(xiàn)原理

前提事件,所謂手勢(shì),就是你的手對(duì)于屏幕觸摸的方向或者說(shuō)軌跡。其實(shí)移動(dòng)端只不過(guò)是用戶的手指代替了 PC 端的鼠標(biāo)。所以如果你做過(guò) PC 端頁(yè)面的鼠標(biāo)軌跡判斷,那移動(dòng)端的手勢(shì)判斷思路也就可以秒殺了。

上面我們提到,要對(duì)用戶手指對(duì)屏幕的操作進(jìn)行捕捉,在 H5 中提供了這樣幾個(gè)關(guān)鍵的事件監(jiān)聽(tīng) touchstart、touchmove、touchend。因?yàn)楸容^基礎(chǔ)我下面就簡(jiǎn)單帶過(guò)了。

首先 touchstart 顧名思義 “觸摸-開(kāi)始”,也就是當(dāng)手指觸摸到屏幕的時(shí)候?qū)⒂|發(fā)這個(gè)事件。其次 touchmove “觸摸-移動(dòng)”。也就是當(dāng)你的手指在屏幕上劃動(dòng)的時(shí)候該事件將不斷被觸發(fā)。最后 touchend “觸摸-結(jié)束” 當(dāng)手指離開(kāi)屏幕的那一瞬間觸發(fā)。

手指事件已經(jīng)解決了,接下來(lái)就是想辦法判斷用戶的手指是怎樣劃動(dòng)的了。這里就需要引入一個(gè)十分基礎(chǔ)的概念坐標(biāo)系。我們數(shù)學(xué)上的平面直角坐標(biāo)系是這樣

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

而我們屏幕上的坐標(biāo)系是這樣的:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

注意 Y 軸的增長(zhǎng)方向和平面直角坐標(biāo)系是相反的。

到這里基本上思路就已經(jīng)確定了,當(dāng)手指觸摸到屏幕的一瞬間也就是 touchstart 時(shí)記錄手指觸摸的位置(xstart,ystart),當(dāng)手指離開(kāi)屏幕時(shí)也就是 touchend 時(shí)獲取手指離開(kāi)屏幕的位置(xend,yend),然后進(jìn)行判斷,如果 xend - xstart > 0 那么水平方向是向右,反之向左。如果 yend - ystart > 0 那么垂直方向是向下,反之向上。當(dāng)然機(jī)器是十分精細(xì)的,就算移動(dòng)一個(gè)像素上述判斷原則也將成立,人類是沒(méi)有機(jī)器那么敏感的,所以這里順便提一個(gè)東西,也就是靈敏度的問(wèn)題,一般來(lái)說(shuō)我們是不希望移動(dòng)一個(gè)像素也算是觸發(fā)了某個(gè)手勢(shì)的,下面講到代碼時(shí)會(huì)有所體現(xiàn)。

手勢(shì)情況圖解:總共有八個(gè)方向

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

三、代碼實(shí)現(xiàn)

由于這次的項(xiàng)目已經(jīng)使用了 zepto.js 這個(gè)框架,所以順手在此基礎(chǔ)上給它添加了自己的手勢(shì)拓展方法。這里我就不再累述給 zepto.js 添加拓展方法的知識(shí)點(diǎn)了。還不了解的朋友請(qǐng)先 Google(說(shuō)到 Google 下篇文章記錄一下如何搭建自己的服務(wù)器利用 ss 科學(xué)上網(wǎng)吧)。我們先上完整代碼,在進(jìn)行核心代碼分析

完整代碼如下:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

$.fn.gesture = function(callback,sensibility){    if (!sensibility || isNaN(sensibility) || sensibility <= 0)
        sensibility = 130;    var start_piont,end_point,delta_x,delta_y,distances;    var direction = {
        horizontal : null,
        vertical : null
    }    // var cond = 130;//靈敏度控制

    this.bind('touchstart',function(e){        var touch = e.touches[0];  
        start_piont = {  
            x: touch.pageX,  
            y: touch.pageY  
        }
    }).bind('touchmove',function(e){        /* var touch = e.touches[0];  
         end_point = {  
              x: touch.pageX,  
              y: touch.pageY  
        }*/
    }).bind('touchend', function(e){  

        var touch = e.changedTouches[0];  
        end_point = {  
            x: touch.pageX,  
            y: touch.pageY  
        }


        delta_x = end_point.x - start_piont.x;  
        delta_y = end_point.y - start_piont.y; 

        if(delta_x < -sensibility) { // 向左劃動(dòng)  
            direction.horizontal='left';
        } else if (delta_x > sensibility) { // 向右劃動(dòng)  
            direction.horizontal='right';
        } 
        if(delta_y > sensibility){
            direction.vertical = 'down';
        }else if(delta_y < (-sensibility)){
            direction.vertical = 'up';  
        }        if(typeof callback === 'function'){
            callback(direction);
        }        //還原狀態(tài)
        direction = {
            horizontal : null,
            vertical : null
        }
    });

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

注意:基于 zepto.js 的拓展方法,所以注意一下依賴。記得先引入 zepto.js

分析:

首先看下下面這句代碼

$.fn.gesture = function(callback,sensibility = 130){...}

sensibility = 130 這個(gè)是 js 的默認(rèn)參數(shù)值,這個(gè)在我之前的文章《開(kāi)源原生JavaScript插件-CJPCD(省市區(qū)聯(lián)動(dòng))》文章中有提到過(guò),后面有點(diǎn)紕漏是 IOS 設(shè)備并不兼容,所以這里還是采用了保險(xiǎn)的做法:

if (!sensibility || isNaN(sensibility) || sensibility <= 0){
    sensibility = 130;
}

sensibility 就是上面提到的靈敏度的問(wèn)題,筆者默認(rèn)設(shè)置為 130 沒(méi)為什么。除此之外代碼中還出現(xiàn)了另外幾個(gè)關(guān)鍵的變量。

  • start_piont: 手指開(kāi)始觸摸的點(diǎn)(x,y)

  • end_piont: 手指j結(jié)束觸摸的點(diǎn)(x,y)

  • delta_x: X 軸的變量增量

  • delta_y: Y 軸的變量增量

  • direction: 方向?qū)ο笫謩?shì)判斷結(jié)果的返回值,包括兩個(gè)成員

    • horizontal : 水平方向,取值為 null、left 或 right

    • vertical : 垂直方向,取值為 null、up 或者 down

代碼值得注意的是:touchstart 和 touchmove 都可以通過(guò) e.touches[0] 來(lái)獲取當(dāng)前的手指坐標(biāo)。但是 touchend 中的 touches (本質(zhì)是 TouchList) 的長(zhǎng)度為零 也就是說(shuō)你無(wú)法通過(guò)這個(gè)來(lái)獲取到手指離開(kāi)時(shí)那個(gè)點(diǎn)的坐標(biāo) 如下圖

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

那這樣需求無(wú)法做了?答案是否定的,我們先來(lái)看方案一

既然這樣我們來(lái) touchmove 事件中做處理,在我們手指在屏幕上劃動(dòng)的時(shí)候會(huì)不斷調(diào)用該方法,那么我們可以在該方法中不斷得給 end_point 刷新它的坐標(biāo)值(x,y)那么當(dāng)我們的手指離開(kāi)屏幕,也就是不再觸發(fā) touchmove 事件,但觸發(fā) touchend 事件,此時(shí)在 touchend 事件中取得 end_point 的坐標(biāo)即得到了手指離開(kāi)屏幕的坐標(biāo)。按此思路下 代碼應(yīng)該修改如下:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

...this.bind('touchstart',function(e){    var touch = e.touches[0];  
    start_piont = {  
        x: touch.pageX,  
        y: touch.pageY  
    }
}).bind('touchmove',function(e){    //在這里不斷刷新
     var touch = e.touches[0];  
     end_point = {  
         x: touch.pageX,  
         y: touch.pageY  
     }
}).bind('touchend', function(e){    //在這里取值計(jì)算
    delta_x = end_point.x - start_piont.x;  
    delta_y = end_point.y - start_piont.y; 

    if(delta_x < -sensibility) { // 向左劃動(dòng)  
        direction.horizontal='left';
    } else if (delta_x > sensibility) { // 向右劃動(dòng)  
        direction.horizontal='right';
    } 
    if(delta_y > sensibility){
        direction.vertical = 'down';
    }else if(delta_y < (-sensibility)){
        direction.vertical = 'up';  
    }    if(typeof callback === 'function'){
        callback(direction);
    }    //還原狀態(tài)
    direction = {
        horizontal : null,
        vertical : null
    }
});

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

這樣做確實(shí)能解決上面我們提到的問(wèn)題。但是從性能上來(lái)講在手指劃動(dòng)的時(shí)候不斷的賦值或者計(jì)算絕對(duì)不是實(shí)現(xiàn)這個(gè)需求的最佳方案,雖然在這里并不會(huì)有明顯的性能問(wèn)題,但是原則上我們還是不要這樣做。所以我們采用下面的方案。 在引出下面方案之前我們先來(lái)介紹一下 changedTouches--‘觸發(fā)事件時(shí)改變的觸摸點(diǎn)的集合’,劃動(dòng)的點(diǎn)自然符合這個(gè)條件,也就是說(shuō)我們可以通過(guò)它來(lái)獲取到手指離開(kāi)屏幕時(shí)的那個(gè)點(diǎn)。不過(guò)這里要聲明一下,這篇文章不涉及多點(diǎn)觸摸的實(shí)現(xiàn),所以我們以單點(diǎn)(一根手指)的觸摸為前提繼續(xù)下面的代碼改造:

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

this.bind('touchstart',function(e){    var touch = e.touches[0];  
    start_piont = {  
        x: touch.pageX,  
        y: touch.pageY  
    }
}).bind('touchmove',function(e){    // 請(qǐng)注意這里代碼清空了}).bind('touchend', function(e){  

    //通過(guò) changedTouches 獲取手指離開(kāi)屏幕時(shí)的坐標(biāo)
    var touch = e.changedTouches[0];  
    end_point = {  
        x: touch.pageX,  
        y: touch.pageY  
    }


    delta_x = end_point.x - start_piont.x;  
    delta_y = end_point.y - start_piont.y; 

    if(delta_x < -sensibility) { // 向左劃動(dòng)  
        direction.horizontal='left';
    } else if (delta_x > sensibility) { // 向右劃動(dòng)  
        direction.horizontal='right';
    } 
    if(delta_y > sensibility){
        direction.vertical = 'down';
    }else if(delta_y < (-sensibility)){
        direction.vertical = 'up';  
    }    if(typeof callback === 'function'){
        callback(direction);
    }    //還原狀態(tài)
    direction = {
        horizontal : null,
        vertical : null
    }
});

平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),游戲開(kāi)發(fā),動(dòng)畫(huà)培訓(xùn)

到這里我們的組件也算基本編寫(xiě)完成了。調(diào)用方式如下

<!--引入依賴-->
<script src="http://cdn.bootcss.com/zepto/1.2.0/zepto.min.js"></script>
<!--引入組件-->
<script src="../plugins/cj-zepto-gesture.min.js"></script>

調(diào)用方法:

//使用案例,90 為靈敏度$('.test-container').gesture(function(direction){    //回調(diào)函數(shù)處理
    console.log('拓展方法',direction);
},90);

test-container 為手勢(shì)操作容器,如下面的藍(lán)色 div、

限于筆者技術(shù),文章觀點(diǎn)難免有不當(dāng)之處,希望發(fā)現(xiàn)問(wèn)題的朋友幫忙指正,筆者將會(huì)及時(shí)更新。也請(qǐng)轉(zhuǎn)載的朋友注明文章出處并附上原文鏈接,以便讀者能及時(shí)獲取到文章更新后的內(nèi)容,以免誤導(dǎo)讀者。筆者力求避免寫(xiě)些晦澀難懂的文章(雖然也有人說(shuō)這樣顯得高逼格,專業(yè)),盡量使用簡(jiǎn)單的用詞和例子來(lái)幫助理解。如果表達(dá)上有好的建議的話也希望朋友們?cè)谠u(píng)論處指出。

本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處! Cboyce

http://www.cnblogs.com/cboyce/p/6732097.html