一、前言
這幾個(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)系是這樣
而我們屏幕上的坐標(biāo)系是這樣的:
注意 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í)現(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)行核心代碼分析
完整代碼如下:
$.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 } });
注意:基于 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) 如下圖
那這樣需求無(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)該修改如下:
...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í)能解決上面我們提到的問(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ù)下面的代碼改造:
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 } });
到這里我們的組件也算基本編寫(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