移动开发框架剖析
一直在寫jQuery的源碼教程,都沒時(shí)間研究別的框架了。Hammer是我項(xiàng)目御用的一個(gè)手勢庫,早期1.x版本的swipe事件的響應(yīng)不靈敏的問題而改過源碼,2.x就已經(jīng)更正過來,而且源碼的結(jié)構(gòu)也整個(gè)翻新了一遍,不管從邏輯還是組織結(jié)構(gòu)上,我個(gè)人都覺得有必要深入,所以就當(dāng)作一個(gè)系列教程一起學(xué)習(xí)吧。
本章主要講解下使用,因?yàn)楣俜降腁PI都是英文的,中文資料也相對較少,源碼的分析后續(xù)再更新。
通過網(wǎng)方的教程還是有很多地方不是很明白的,可能需要后期看源碼才能弄懂了。
Hammer.js是一個(gè)專門用于控制、定制手勢的JavaScript庫。它可以識別出常見的觸摸、拖動、長按、縮放等等,對于希望在網(wǎng)頁上對手勢有所處理的朋友們,應(yīng)該很有幫助。
官方也表明了2.0版本是完全重寫,包括手勢識別器、和改進(jìn)支持最近的移動瀏覽器利用touch-action css屬性。同時(shí)還支持多種設(shè)備,所以多用戶成為可能。功能上更加強(qiáng)大了
使用上很簡單,引入源碼并且創(chuàng)建一個(gè)實(shí)例。
?
Hammer
var hammertime = new Hammer(myElement, myOptions); hammertime.on('pan', function(ev) {console.log(ev); });默認(rèn)設(shè)置下自動添加了,tap、doubletap、press,pan與swipe的橫向滑動,多點(diǎn)觸摸pinch與rotate手勢。
pinch和rotate識別器在默認(rèn)情況下都是禁用的,因?yàn)樗麄儠性刈枞?但是我們可以手動開啟:
hammertime.get('pinch').set({ enable: true }); hammertime.get('rotate').set({ enable: true });當(dāng)然,我們還可以為pan與swipe 開啟縱向滑動
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); hammertime.get('swipe').set({ direction: Hammer.DIRECTION_VERTICAL我們能通過meta的標(biāo)記,禁用doubletap /觸控放大。但是新的瀏覽器支持touch-action屬性所以可以不需要這個(gè)
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">?
1.基本的實(shí)現(xiàn)
綁定一個(gè)簡單的swipe事件,通過回調(diào)得到事件的響應(yīng)
var mc = new Hammer(el);mc.on('swipe',function(evt){console.log(evt) })?
2.改變事件處理的方向
但是默認(rèn)情況下Hammer是屏蔽了上下滑動的響應(yīng)的,所以我們?nèi)绻恢肛Q向響應(yīng),就需要再配置中設(shè)置
mc.get('pan').set({direction: Hammer.DIRECTION_VERTICAL });mc.on('swipe',function(evt){console.log(evt) })我們還可以同時(shí)響應(yīng)橫向與豎向,除此之外,還可以單獨(dú)為某個(gè)指定的識別器配置
var mc = new Hammer(el);mc.get('swipe').set({direction: Hammer.DIRECTION_ALL });mc.on('swipe pan',function(evt){console.log(evt) })通過get方法我們可以得到指定對應(yīng)的識別器,我們這里只給swipe啟動了上下左右滑動的響應(yīng),那么pan事件則沒開啟,這種指定特定事件的處理相當(dāng)?shù)撵`活
當(dāng)然以上都是比較簡單常見的事件處理,如果在一個(gè)元素上綁定多個(gè)不同的事件處理,那可以引入Hammer.Manager控制了
?
3.Hammer.Manager
我們可以通過這個(gè)Manager設(shè)置自己的識別器的實(shí)例??梢栽O(shè)置更多的被識別的手勢。
一個(gè)復(fù)雜的多事件處理的Example?https://cdn.rawgit.com/hammerjs/hammer.js/master/tests/manual/visual.html
var mc = new Hammer.Manager(myElement, myOptions);mc.add( new Hammer.Pan({ direction: Hammer.DIRECTION_ALL, threshold: 0 }) ); mc.add( new Hammer.Tap({ event: 'quadrupletap', taps: 4 }) );mc.on("pan", handlePan); mc.on("quadrupletap", handleTaps);上面的示例創(chuàng)建一個(gè)實(shí)例包含一個(gè)pan和一個(gè)quadrupletap手勢
當(dāng)然如果我們同時(shí)給一個(gè)元素上 綁定多個(gè)事件用new Hammer(el);直接通過on方法也是可以實(shí)現(xiàn)的
但是實(shí)際上的測試效果,識別度與靈活度比Hammer.Manager低很多。
因?yàn)镸anager控制里面,引入了recognizeWith與requireFailure用來關(guān)聯(lián)2個(gè)相近的事件,從而提高可用性
var pinch = new Hammer.Pinch(); var rotate = new Hammer.Rotation(); pinch.recognizeWith(rotate);當(dāng)然具體內(nèi)部如何實(shí)現(xiàn),要等以后源碼分解才知道了。官方給的說明確實(shí)太少了
最后官方還提到提供一個(gè)神秘hammer.input事件,在每一次有用戶交互的時(shí)候都會被觸發(fā),可以得到非常有用的數(shù)據(jù)
hammertime.on("hammer.input", function(ev) {console.log(ev.pointers); });?
除此之外,還有很多參數(shù)的
比如事件對象,Directions方向,輸入動作Input Events,識別器狀態(tài)等等
?
提供的方法Utils
類似addEventListener的事件綁定與銷毀
Hammer.on(window, "load resize scroll", function(ev) {console.log(ev.type); });遍歷
Hammer.each([10,20,30,40], function(item, index, src) { });合并
var options = {b: false };var defaults = {a: true,b: true,c: [1,2,3] }; Hammer.merge(options, defaults);// options.a == true // options.b == false // options.c == [1,2,3]extend,inherit,bindFn等等
hammerjs確實(shí)很強(qiáng)大,做了市面上大多數(shù)的設(shè)備的適配,我可以看到源碼中關(guān)于適配的代碼就接近一半,整個(gè)源碼的結(jié)構(gòu)其實(shí)也是比較規(guī)整的
大多數(shù)人能用1,2步的處理就已經(jīng)完全足夠了,但是這都不是重點(diǎn),重點(diǎn)是我們要的不是使用,而且能夠“造輪子”弄懂原理!
瀏覽器底層并沒有給元素提供類似,單擊,雙擊,滑動,拖動這些直接可以用的控制接口,一切的手勢動作都只能通過模擬出來。移動端瀏覽器唯一給我們提供的就只是mousedown -> mousemove -> mouseup三種最基本的事件接口。那么我們只能通過這些簡單的接口模擬出復(fù)雜的手勢出來。
常規(guī)的做法流程:
1.給元素上綁定三個(gè)事件,mousedown ,mousemove,mouseup
2.在交互的時(shí)候,用戶只觸發(fā)mousedown,mouseup沒有觸發(fā)mousemove,就可以單算是一次點(diǎn)擊的動作, 這里可以是 單擊,雙擊與長按,具體可以通過間隔的時(shí)間判斷
3.如果mousemove觸發(fā)了,自然就是滑動與拖動了
當(dāng)然手勢還要涉及到多點(diǎn)操作縮放與旋轉(zhuǎn)的處理,就之后在討論。
?
先拋開流程,我們要知道設(shè)計(jì)一個(gè)手勢庫需要考慮的問題,至少有2點(diǎn):
1.運(yùn)行的平臺
2.用戶的手勢
?
那么我們可以總結(jié)市面上的終端設(shè)備有那么三種:
1 手機(jī)/pad移動端
2 pc類
3 還有種帶觸摸屏的電腦一類
?
用戶的手勢行為大體分:
單擊tap , 雙擊doubletap,平移pan,滑動swipe,長按press,縮放pinch,旋轉(zhuǎn)rotate
?
從設(shè)計(jì)的角度來講,有著不同的兼容與選擇問題,卻又有著一些相同的共性與處理我們要如何去組織結(jié)構(gòu)?
當(dāng)然依舊是OOP設(shè)計(jì)了,抽出父類,實(shí)現(xiàn)繼承,引入策略模式
我們看看Hammer在結(jié)構(gòu)上是如何實(shí)現(xiàn)這類設(shè)計(jì)的
常規(guī)來說手勢的處理,要分為初始化與執(zhí)行期。初始化的時(shí)候構(gòu)建所有相關(guān)的參與與方法
hammer源碼里面分幾大塊:
1. Hammer類,一個(gè)簡單的工廠方法,用來創(chuàng)建一個(gè)管理和初始化默認(rèn)的識別器。Hammer.defaults配置一些基本的選項(xiàng)
包括針對每種識別器的配置與元素CSS屬性的設(shè)置
2. Manager類,整個(gè)庫的管理類。內(nèi)部初始化了input輸入對象,所有手勢對象,元素css設(shè)置對象touchAction
3. InputHandler類,事件回調(diào)的具體加工類,用來生成包裝后的事件對象與派發(fā)事件到每一個(gè)識別器
4. TouchAction類,設(shè)置元素的touchAction屬性
5. Input類,事件處理類。用來處理綁定與銷毀,事件句柄的回調(diào)。每一個(gè)輸入類都需要繼承
6. Recognizer類,所有識別器需要繼承的基類
以上就是整個(gè)庫的類塊了,當(dāng)然5與6都是屬于基類繼承的,在代碼運(yùn)行的時(shí)候就自動構(gòu)建完畢了
?
關(guān)于繼承inherit方法
hammer用的是傳統(tǒng)的類似繼承
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);} }只繼承了原型的方法,因?yàn)樵投际枪蚕淼?#xff0c;如果放置屬性可以被任何一個(gè)繼承的子類所有修改,所以屬性的繼承需要用call方法
繼承的子類有一個(gè)私有屬性 _super指向父類,同時(shí)還能額外的擴(kuò)展方法
inherit(MouseInput, Input, {handler: function MEhandler(ev){} }子類MouseInput繼承父類Input類的所有原型方法,并擴(kuò)展了handler方法
?
輸入設(shè)備初始化繼承:就是通過什么設(shè)備觸發(fā)動作(PC,手機(jī),ipad等等)
輸入設(shè)備hammer分為
MouseInput,PointerEventInput,SingleTouchInput,TouchInput,TouchMouseInput
我們看看最簡單的桌面PC的鼠標(biāo)輸入處理MouseInput,其余的結(jié)構(gòu)基本類似。
function MouseInput() {this.evEl = MOUSE_ELEMENT_EVENTS;this.evWin = MOUSE_WINDOW_EVENTS;//用來禁止TouchMouse事件this.allow = true; // used by Input.TouchMouse to disable mouse events//鼠標(biāo)按下的狀態(tài)this.pressed = false; // mousedown stateInput.apply(this, arguments); }inherit(MouseInput, Input, {handler: function MEhandler(ev){..// } }MouseInput初始化了幾個(gè)必要的判斷屬性,然后就只handler方法, 此外還集成了Input輸入類
比如我們調(diào)用
new MouseInput(callback)的時(shí)候,通過Input.apply(this, arguments)去初始化了基類input類,然后基類內(nèi)部的init綁定了事件,并且把事件的回調(diào),
this.domHandler指向了外部的callback回調(diào),其實(shí)也就是handler方法了
addEventListeners(this.element, this.evEl, this.domHandler);?
另一個(gè)基類就是Recognizer
因?yàn)槲覀儼延脩舻男袨榉譃閱螕魌ap , 雙擊doubletap,平移pan,滑動swipe,長按press,縮放pinch,旋轉(zhuǎn)rotate,那么類似相同點(diǎn)我們也必須抽象成一個(gè)基類
Recognizer比如復(fù)雜,留在執(zhí)行期的時(shí)候講解。
?
Hammer 使用:
var mc = new Hammer(el);那么內(nèi)部的構(gòu)建
function Hammer(element, options) {options = options || {};//配置手勢識別器參數(shù)options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);return new Manager(element, options); }可見最終是Manager構(gòu)建的對象實(shí)例了
Manager內(nèi)部,通過createInputInstance創(chuàng)建一個(gè)輸入環(huán)境的實(shí)例對象,創(chuàng)建一個(gè)輸入環(huán)境的實(shí)例對象
this.input = createInputInstance(this);createInputInstance的作用主要是用來選擇當(dāng)然的平臺,不同的平臺會調(diào)用不同的手勢輸入處理,這里就有策略選擇了的處理了
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; //移動手機(jī)端} else if (!SUPPORT_TOUCH) {Type = MouseInput; //桌面} else {Type = TouchMouseInput;} }如果是桌面PC端,我們就會走M(jìn)ouseInput
return new MouseInput(manager, inputHandler);這樣把具體的通過Input類綁定的回調(diào)放到MouseInput的handler處理了,最終的回調(diào)會進(jìn)入總處理inputHandler類
inputHandler類就會遍歷所有的手勢識別器把輸入的input傳入
manager.recognize(input);每一個(gè)識別器各自處理其行為了,當(dāng)然這里面倒是如何觸發(fā),手勢識別器如何判斷是那種手勢,就放一章了。
總結(jié)
- 上一篇: Redis源码简要分析
- 下一篇: C shell命令行解释器