這幾個月事情比較多,寫了一些博客都沒有來得及整理髮布,今天恰好有一位同事在開發前端頁面的時候用到了手勢判斷。因此翻出了以前寫的 demo,順便整理一下做爲記錄。css
手勢判斷在各類應用中都十分常見,如 APP 中的手勢翻頁,前進後退等等,如微博作得就特別好,微信的話就不想吐槽了。不扯太遠,H5 開發中手勢判斷通常多用於一些交互比較靈活的場景,例如大轉盤抽獎遊戲,旋轉菜單,酷跑,打磚塊遊戲等等。今天不具體到這些小遊戲的開發,咱們重點講講實現的原理。其實比較基礎,大神請自動忽略。html
前提事件,所謂手勢,就是你的手對於屏幕觸摸的方向或者說軌跡。其實移動端只不過是用戶的手指代替了 PC 端的鼠標。因此若是你作過 PC 端頁面的鼠標軌跡判斷,那移動端的手勢判斷思路也就能夠秒殺了。前端
上面咱們提到,要對用戶手指對屏幕的操做進行捕捉,在 H5 中提供了這樣幾個關鍵的事件監聽 touchstart
、touchmove
、touchend
。由於比較基礎我下面就簡單帶過了。web
首先 touchstart
顧名思義 「觸摸-開始」,也就是當手指觸摸到屏幕的時候將觸發這個事件。其次 touchmove
「觸摸-移動」。也就是當你的手指在屏幕上划動的時候該事件將不斷被觸發。最後 touchend
「觸摸-結束」 當手指離開屏幕的那一瞬間觸發。canvas
手指事件已經解決了,接下來就是想辦法判斷用戶的手指是怎樣划動的了。這裏就須要引入一個十分基礎的概念座標系。咱們數學上的平面直角座標系是這樣ruby
而咱們屏幕上的座標系是這樣的:服務器
注意 Y 軸的增加方向和平面直角座標系是相反的。微信
到這裏基本上思路就已經肯定了,當手指觸摸到屏幕的一瞬間也就是 touchstart
時記錄手指觸摸的位置(xstart,ystart),當手指離開屏幕時也就是 touchend
時獲取手指離開屏幕的位置(xend,yend),而後進行判斷,若是 xend - xstart > 0 那麼水平方向是向右,反之向左。若是 yend - ystart > 0 那麼垂直方向是向下,反之向上。固然機器是十分精細的,就算移動一個像素上述判斷原則也將成立,人類是沒有機器那麼敏感的,因此這裏順便提一個東西,也就是靈敏度的問題,通常來講咱們是不但願移動一個像素也算是觸發了某個手勢的,下面講到代碼時會有所體現。app
手勢狀況圖解:總共有八個方向框架
因爲此次的項目已經使用了 zepto.js 這個框架,因此順手在此基礎上給它添加了本身的手勢拓展方法。這裏我就再也不累述給 zepto.js 添加拓展方法的知識點了。還不瞭解的朋友請先 Google(說到 Google 下篇文章記錄一下如何搭建本身的服務器利用 ss ***吧)。咱們先上完整代碼,在進行核心代碼分析
完整代碼以下:
$.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) { // 向左划動 direction.horizontal='left'; } else if (delta_x > sensibility) { // 向右划動 direction.horizontal='right'; } if(delta_y > sensibility){ direction.vertical = 'down'; }else if(delta_y < (-sensibility)){ direction.vertical = 'up'; } if(typeof callback === 'function'){ callback(direction); } //還原狀態 direction = { horizontal : null, vertical : null } });
注意:基於 zepto.js 的拓展方法,因此注意一下依賴。記得先引入 zepto.js
分析:
首先看下下面這句代碼
$.fn.gesture = function(callback,sensibility = 130){...}
sensibility = 130
這個是 js 的默認參數值,這個在我以前的文章《開源原生JavaScript插件-CJPCD(省市區聯動)》文章中有提到過,後面有點紕漏是 IOS 設備並不兼容,因此這裏仍是採用了保險的作法:
if (!sensibility || isNaN(sensibility) || sensibility <= 0){ sensibility = 130; }
sensibility
就是上面提到的靈敏度的問題,筆者默認設置爲 130 沒爲何。除此以外代碼中還出現了另外幾個關鍵的變量。
null
、left
或 right
null
、up
或者 down
代碼值得注意的是:touchstart
和 touchmove
均可以經過 e.touches[0]
來獲取當前的手指座標。可是 touchend
中的 touches
(本質是 TouchList) 的長度爲零 也就是說你沒法經過這個來獲取到手指離開時那個點的座標 以下圖
既然這樣咱們來 touchmove 事件中作處理,在咱們手指在屏幕上划動的時候會不斷調用該方法,那麼咱們能夠在該方法中不斷得給 end_point
刷新它的座標值(x,y)那麼當咱們的手指離開屏幕,也就是再也不觸發 touchmove
事件,但觸發 touchend
事件,此時在 touchend
事件中取得 end_point
的座標即獲得了手指離開屏幕的座標。按此思路下 代碼應該修改以下:
... 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){ //在這裏取值計算 delta_x = end_point.x - start_piont.x; delta_y = end_point.y - start_piont.y; if(delta_x < -sensibility) { // 向左划動 direction.horizontal='left'; } else if (delta_x > sensibility) { // 向右划動 direction.horizontal='right'; } if(delta_y > sensibility){ direction.vertical = 'down'; }else if(delta_y < (-sensibility)){ direction.vertical = 'up'; } if(typeof callback === 'function'){ callback(direction); } //還原狀態 direction = { horizontal : null, vertical : null } });
這樣作確實能解決上面咱們提到的問題。可是從性能上來說在手指划動的時候不斷的賦值或者計算絕對不是實現這個需求的最佳方案,雖然在這裏並不會有明顯的性能問題,可是原則上咱們仍是不要這樣作。因此咱們採用下面的方案。 在引出下面方案以前咱們先來介紹一下 changedTouches
--‘觸發事件時改變的觸摸點的集合’,划動的點天然符合這個條件,也就是說咱們能夠經過它來獲取到手指離開屏幕時的那個點。不過這裏要聲明一下,這篇文章不涉及多點觸摸的實現,因此咱們以單點(一根手指)的觸摸爲前提繼續下面的代碼改造:
this.bind('touchstart',function(e){ var touch = e.touches[0]; start_piont = { x: touch.pageX, y: touch.pageY } }).bind('touchmove',function(e){ // 請注意這裏代碼清空了 }).bind('touchend', function(e){ //經過 changedTouches 獲取手指離開屏幕時的座標 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) { // 向左划動 direction.horizontal='left'; } else if (delta_x > sensibility) { // 向右划動 direction.horizontal='right'; } if(delta_y > sensibility){ direction.vertical = 'down'; }else if(delta_y < (-sensibility)){ direction.vertical = 'up'; } if(typeof callback === 'function'){ callback(direction); } //還原狀態 direction = { horizontal : null, vertical : null } });
到這裏咱們的組件也算基本編寫完成了。調用方式以下
<!--引入依賴--> <script src="http://cdn.bootcss.com/zepto/1.2.0/zepto.min.js"></script> <!--引入組件--> <script src="../plugins/cj-zepto-gesture.min.js"></script>
調用方法:
//使用案例,90 爲靈敏度 $('.test-container').gesture(function(direction){ //回調函數處理 console.log('拓展方法',direction); },90);
test-container
爲手勢操做容器,以下面的藍色 div、
限於筆者技術,文章觀點不免有不當之處,但願發現問題的朋友幫忙指正,筆者將會及時更新。也請轉載的朋友註明文章出處並附上原文連接,以便讀者能及時獲取到文章更新後的內容,以避免誤導讀者。筆者力求避免寫些晦澀難懂的文章(雖然也有人說這樣顯得高逼格,專業),儘可能使用簡單的用詞和例子來幫助理解。若是表達上有好的建議的話也但願朋友們在評論處指出。
本文爲做者原創,轉載請註明出處! Cboyce