瀏覽器底層並沒有給元素提供類似,單擊,雙擊,滑動,拖動這些直接可以用的控制接口,一切的手勢動作都只能通過模擬出來。移動端瀏覽器唯一給我們提供的就只是mousedown -> mousemove -> mouseup三種最基本的事件接口。那么我們只能通過這些簡單的接口模擬出復雜的手勢出來。
常規的做法流程:
1.給元素上綁定三個事件,mousedown ,mousemove,mouseup
2.在交互的時候,用戶只觸發mousedown,mouseup沒有觸發mousemove,就可以單算是一次點擊的動作, 這里可以是 單擊,雙擊與長按,具體可以通過間隔的時間判斷
3.如果mousemove觸發了,自然就是滑動與拖動了
當然手勢還要涉及到多點操作縮放與旋轉的處理,就之后在討論。
先拋開流程,我們要知道設計一個手勢庫需要考慮的問題,至少有2點:
1.運行的平台
2.用戶的手勢
那么我們可以總結市面上的終端設備有那么三種:
1 手機/pad移動端
2 pc類
3 還有種帶觸摸屏的電腦一類
用戶的手勢行為大體分:
單擊tap , 雙擊doubletap,平移pan,滑動swipe,長按press,縮放pinch,旋轉
rotate
從設計的角度來講,有着不同的兼容與選擇問題,卻又有着一些相同的共性與處理我們要如何去組織結構?
當然依舊是OOP設計了,抽出父類,實現繼承,引入策略模式
我們看看Hammer在結構上是如何實現這類設計的
常規來說手勢的處理,要分為初始化與執行期。初始化的時候構建所有相關的參與與方法
hammer源碼里面分幾大塊:
1. Hammer類,一個簡單的工廠方法,用來創建一個管理和初始化默認的識別器。Hammer.defaults配置一些基本的選項
包括針對每種識別器的配置與元素CSS屬性的設置
2. Manager類,整個庫的管理類。內部初始化了input輸入對象,所有手勢對象,元素css設置對象touchAction
3. InputHandler類,事件回調的具體加工類,用來生成包裝后的事件對象與派發事件到每一個識別器
4. TouchAction類,設置元素的touchAction屬性
5. Input類,事件處理類。用來處理綁定與銷毀,事件句柄的回調。每一個輸入類都需要繼承
6. Recognizer類,所有識別器需要繼承的基類
以上就是整個庫的類塊了,當然5與6都是屬於基類繼承的,在代碼運行的時候就自動構建完畢了
關於繼承inherit方法
hammer用的是傳統的類似繼承
function inherit(child, base, properties) { var baseP = base.prototype, childP; childP = child.prototype = Object.create(baseP); childP.constructor = child; childP._super = baseP; if (properties) { extend(childP, properties); } }
只繼承了原型的方法,因為原型都是共享的,如果放置屬性可以被任何一個繼承的子類所有修改,所以屬性的繼承需要用call方法
繼承的子類有一個私有屬性 _super指向父類,同時還能額外的擴展方法
inherit(MouseInput, Input, { handler: function MEhandler(ev){ } }
子類MouseInput繼承父類Input類的所有原型方法,並擴展了handler方法
輸入設備初始化繼承:就是通過什么設備觸發動作(PC,手機,ipad等等)
輸入設備hammer分為
MouseInput,PointerEventInput,SingleTouchInput,TouchInput,TouchMouseInput
我們看看最簡單的桌面PC的鼠標輸入處理MouseInput,其余的結構基本類似。
function MouseInput() { this.evEl = MOUSE_ELEMENT_EVENTS; this.evWin = MOUSE_WINDOW_EVENTS; //用來禁止TouchMouse事件 this.allow = true; // used by Input.TouchMouse to disable mouse events //鼠標按下的狀態 this.pressed = false; // mousedown state Input.apply(this, arguments); } inherit(MouseInput, Input, { handler: function MEhandler(ev){ ..////////////// } }
MouseInput初始化了幾個必要的判斷屬性,然后就只handler方法, 此外還集成了Input輸入類
比如我們調用
new MouseInput(callback)的時候,通過Input.apply(this, arguments)去初始化了基類input類,然后基類內部的init綁定了事件,並且把事件的回調,
this.domHandler指向了外部的callback回調,其實也就是handler方法了
addEventListeners(this.element, this.evEl, this.domHandler);
另一個基類就是Recognizer
因為我們把用戶的行為分為單擊tap , 雙擊doubletap,平移pan,滑動swipe,長按press,縮放pinch,旋轉
rotate,那么類似相同點我們也必須抽象成一個基類
Recognizer比如復雜,留在執行期的時候講解。
Hammer 使用:
var mc = new Hammer(el);
那么內部的構建
function Hammer(element, options) { options = options || {}; //配置手勢識別器參數 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset); return new Manager(element, options); }
可見最終是Manager構建的對象實例了
Manager內部,通過createInputInstance創建一個輸入環境的實例對象,創建一個輸入環境的實例對象
this.input = createInputInstance(this);
createInputInstance的作用主要是用來選擇當然的平台,不同的平台會調用不同的手勢輸入處理,這里就有策略選擇了的處理了
function createInputInstance(manager) { var Type; var inputClass = manager.options.inputClass; if (inputClass) { Type = inputClass; } else if (SUPPORT_POINTER_EVENTS) { Type = PointerEventInput; } else if (SUPPORT_ONLY_TOUCH) { Type = TouchInput; //移動手機端 } else if (!SUPPORT_TOUCH) { Type = MouseInput; //桌面 } else { Type = TouchMouseInput; } }
如果是桌面PC端,我們就會走MouseInput
return new MouseInput(manager, inputHandler);
這樣把具體的通過Input類綁定的回調放到MouseInput的handler處理了,最終的回調會進入總處理inputHandler類
inputHandler類就會遍歷所有的手勢識別器把輸入的input傳入
manager.recognize(input);
每一個識別器各自處理其行為了,當然這里面倒是如何觸發,手勢識別器如何判斷是那種手勢,就放一章了。
我的:https://github.com/JsAaron/hammer-js