摘要:最后一點思考都是在冒泡階段綁定事件處理器,為什么不在捕獲階段攔截事件尼,如果一個向右活動的手勢被識別,后續的事件如已經沒必要再傳給子節點,完全可以在攔截的元素上處理,這樣性能上也應該會有一點提升,挖個坑給自己以后實現一下。
開始
話說上周周末閑的蛋疼,突然想了解一下前端手勢如何處理,好解開自己一個知識盲點,于是開始啃源碼。。。并紀錄一下。
一個手勢在我們的前端頁面里面復雜的手勢應該是不多見的,一般常用就是拖拉,雙擊,放大縮小這幾個,但是合理運用手勢很明顯也能為我們頁面的交互體驗有一點增色,那么問題來了,如何識別一個手勢尼?
Hammer.jsHammer.js 應該算是前端使用的比較廣泛的一個手勢框架了(我所了解的還有一個AlloyTouch,更小,當然它提供的抽象程度是不如Hammer.js的),今天就拿這個框架來開刀吧。
配置參數我們先來看Hammer.js的配置參數:
{ //手勢事件觸發時,是否同時觸發對應的一個自定義的dom事件,當然這個沒有直接綁定事件回調高效 domEvents: false, //這個會影響對應的css屬性touch-action的值,下面會接著說 touchAction: TOUCH_ACTION_COMPUTE, enable: true, //是否開啟手勢識別 //可以指定在其他的元素上來檢測與touch相關的事件并作為輸入源,如果沒設置就是當前檢測的元素了 inputTarget: null, inputClass: null, //輸入源類型,鼠標還是觸摸或者是混合 recognizers: [], //我們配置的手勢識別器 //預設的一些手勢識別器,格式:[RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]] preset: [ [RotateRecognizer, { enable: false }], [PinchRecognizer, { enable: false }, ["rotate"]], [SwipeRecognizer, { direction: DIRECTION_HORIZONTAL }], [PanRecognizer, { direction: DIRECTION_HORIZONTAL }, ["swipe"]], [TapRecognizer], [TapRecognizer, { event: "doubletap", taps: 2 }, ["tap"]], [PressRecognizer] ], cssProps: { //額外的一些css屬性 userSelect: "none", touchSelect: "none", touchCallout: "none", contentZooming: "none", userDrag: "none", tapHighlightColor: "rgba(0,0,0,0)" } }
總的來說配置參數不多,也不算復雜,這個框架基本也算是開箱即用了,好,我們接著再深入一點。
初始化接著來到源碼里面manager.js,可以看到以下一段的代碼:
export default class Manager { constructor() { ... this.element = element; this.input = createInputInstance(this);// 1 this.touchAction = new TouchAction(this,this.options.touchAction);// 2 toggleCssProps(this, true); each(this.options.recognizers, (item) => { //3 let recognizer = this.add(new (item[0])(item[1])); item[2] && recognizer.recognizeWith(item[2]); item[3] && recognizer.requireFailure(item[3]); }, this); } ... }
1.新建一個輸入源
根據設備的不同手勢可能是來自鼠標也有可能來自手機上的觸摸屏,而且mouse event的屬性和touch event的屬性是有一絲差異的(還有pointer event),所以為了方便后續處理,Hammer.js也分別定義了不同類型輸入源:MouseInput,PointerEventInput,SingleTouchInput,TouchInput和TouchMouseInput;并針對不同的事件,對參數做了一個簡單處理(handler方法),最終得到統一格式的數據輸出,就像這樣:
{ pointers: touches[0], changedPointers: touches[1], pointerType: INPUT_TYPE_TOUCH, srcEvent: ev }
在獲取統一格式的輸入數據后,會交由InputHandler進一步處理,會判斷這次輸入是手勢的開始還是結束,如果是開始就會新建一個手勢識別的session,并且計算一些與手勢相關的數據(角度,偏移距離,移動方向等),具體可以在compute-input-data.js里面看到。
經過以這一輪計算,我們已經有足夠的數據來支持之后的手勢識別了。
另外一提的是,這五種輸入源都繼承了Input,在Input里面事件是這樣綁定的:
this.evEl && addEventListeners(this.element, this.evEl, this.domHandler); this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler); this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
有三種綁定目標,當前的element,inputTarget,element所屬的window,在window上綁定事件處理器還是很必要的(例如拖拉一個元素的時候);另外翻了一下代碼,inputTarget綁定都是touch相關的事件,不是很明白它的意圖和場景,為什么要分離一個目標多帶帶處理觸摸事件。
2.設置元素樣式里touch-action的值
在手機瀏覽器里面,一般也會自帶一些手勢處理,例如向右滑動或者向左滑動就是前進和后退,所以除了我們自己定義手勢,還需要對瀏覽器的手勢做一些限制或者禁止。
這里也舉個栗子吧,在Hammer.js里面默認提供拖拉手勢的識別器(就是pan.js),當在檢測水平方向的拖拉的時候,這個識別器會把touch-action的值設為pay-y(允許瀏覽器處理垂直方向的拖拉,可以是一個垂直的滾動或者其他),那如果我又接著定義一個垂直方向拖拉的識別器時,touch-action的值是多少尼?(答案就是none,瀏覽器不會幫我們再處理了,垂直方向滾動也只能靠自己),那是怎樣計算出來的尼?
在創建TouchAction對象時,如果配置參數中touchAction的值為TOUCH_ACTION_COMPUTE,便調用compute方法開始遍歷recognizers,收集它們所希望設置的touch-action的值:
compute() { let actions = []; each(this.manager.recognizers, (recognizer) => { if (boolOrFn(recognizer.options.enable, [recognizer])) { actions = actions.concat(recognizer.getTouchAction()); } }); return cleanTouchActions(actions.join(" ")); }
最終在cleanTouchActions方法集中計算最終的值:
... let hasPanX = inStr(actions, TOUCH_ACTION_PAN_X); let hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y); if (hasPanX && hasPanY) { return TOUCH_ACTION_NONE; } ...
3.配置手勢識別器
主要是配置各個手勢識別器之間的關系,是否可以協同還是互斥,用官網一個例子:
var hammer = new Hammer(el, {}); var singleTap = new Hammer.Tap({ event: "singletap" }); var doubleTap = new Hammer.Tap({event: "doubletap", taps: 2 }); var tripleTap = new Hammer.Tap({event: "tripletap", taps: 3 }); hammer.add([doubleTap, doubleTap, singleTap]); tripleTap.recognizeWith([doubleTap, singleTap]); doubleTap.recognizeWith(singleTap); doubleTap.requireFailure(tripleTap); singleTap.requireFailure([tripleTap, doubleTap]);
以上定義了三個手勢識別器:singleTap,doubleTap和tripleTap,很明顯這個三個識別器是互斥的,如果用戶點三下屏幕時都觸發就比較尷尬了;
這里得注意添加的順序,因為Hammer.js是會按順序遍歷識別器調用他們的recognize方法,因為我們已經設置了手勢的互斥,Hammer.js為了知道手勢是單擊還是雙擊,singleTap,doubleTap,tripleTap識別器都設置了300ms等待時間來判斷之后還會不會有點擊事件,根據識別順序,singleTap總能獲取tripleTap和doubleTap的識別結果來判斷是否要觸發事件,假如我們不設置他們之間的互斥關系,Hammer.js默認一滿足條件就會觸發,就會出現剛才說的那種尷尬的場景。
那recognizeWith有啥作用尼,看以下代碼:
if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) { curRecognizer = session.curRecognizer = null; } let i = 0; while (i < recognizers.length) { recognizer = recognizers[i]; if (session.stopped !== FORCED_STOP && ( !curRecognizer || recognizer === curRecognizer || recognizer.canRecognizeWith(curRecognizer))) { recognizer.recognize(inputData); } else { recognizer.reset(); } if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) { curRecognizer = session.curRecognizer = recognizer; } i++; }
雖然singleTap,doubleTap和tripleTap從最終結果上應該是互斥的,但是同樣的數據輸入時可能會同時讓幾個手勢識別器識別,例如當用戶點擊一下屏幕,singleTap識別器的狀態可能是STATE_RECOGNIZED或者STATE_BEGAN(等待doubleTap和tripleTap識別器的結果),session會把singTap識別器記錄為當前的手勢識別器,但是doubleTap和tripleTap也是需要記錄一些狀態(例如當前點擊次數),因為很有可能接下來又是一個單擊,變成雙擊手勢;當用戶接著再單擊一下,doubleTap識別器因為設置了recognizeWith(singleTap)和以協同singleTap識別數據輸入,然后doubleTap識別器開始進入STATE_RECOGNIZED或者STATE_BEGAN(等待tripleTap識別器的結果),此時session當前的手勢識別器就是doubleTap了,而singleTap識別器因為沒有設置recognizeWith(doubleTap),會被重置。
一點小的細節我們在旋轉一張圖片時,如何實現旋轉,怎么知道旋轉的角度尼?
再回到computeInputData方法,有這樣一行代碼獲取偏轉角度:
... let center = input.center = getCenter(pointers); ... input.angle = getAngle(offsetCenter, center); ...
再跟蹤一下getCenter方法:
while (i < pointersLength) { x += pointers[i].clientX; y += pointers[i].clientY; i++; } return { x: round(x / pointersLength), y: round(y / pointersLength) };
很簡單的算出手勢的中心位置,當我們雙指旋轉時,中心位置也會跟著移動,很容易計算出前后偏轉角度。
最后一點思考Hammer.js都是在冒泡階段綁定事件處理器,為什么不在捕獲階段攔截事件尼,如果一個向右活動的手勢被識別,后續的事件(如touchMove)已經沒必要再傳給子節點,完全可以在攔截的元素上處理,這樣性能上也應該會有一點提升,挖個坑給自己以后實現一下。
最后的最后。。。
因為沒有使用經驗,單靠啃源碼,難免有所錯漏,望指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82562.html
摘要:一直以來都想著去詳細了解手勢事件相關的東西,又因為一直以來使用的都是,所以想著通過閱讀的源碼來學習手勢的相關知識。 一直以來都想著去詳細了解手勢事件相關的東西,又因為一直以來使用的都是Hammer.js,所以想著通過閱讀Hammer.js的源碼來學習手勢的相關知識。 首先,我們來看Hammer.js的整體架構(Hammer.js的版本都是2.0.8)showImg(https://se...
摘要:是一個為應用添加觸摸手勢的非常受歡迎的庫文中將看到結合一起使用是多么的簡單原文示例是針對版本經過測試在目前最新的版本中此教程依然適用文章將以來統一代稱版本名詞滑動和類似但滑動更快速無粘滯左滑右滑上滑下滑頭像輪播簡介我們將構建一個頭像輪播可以 HammerJS 是一個為 web 應用添加觸摸手勢的非常受歡迎的庫,文中,將看到 Angular 結合 HammerJS 一起使用是多么的簡單 ...
摘要:一個移動端的手勢庫。的過程最簡單的使用一個手勢的定義綁定事件調用初始化在中可以看到下面一段代碼用于定義一個手勢操作的元素定義配置參數定義如果為默認默認同步注冊了同理同步注冊也可以外部采用注冊同步綁定事件中的實際上為調用中的 hammerjs ———— 一個移動端的手勢庫。 New Hammer 的過程 最簡單的使用一個手勢的demo // 定義 Manager var hammer...
閱讀 3776·2021-09-02 09:53
閱讀 2753·2021-07-30 14:57
閱讀 3497·2019-08-30 13:09
閱讀 1200·2019-08-29 13:25
閱讀 813·2019-08-29 12:28
閱讀 1459·2019-08-29 12:26
閱讀 1133·2019-08-28 17:58
閱讀 3308·2019-08-26 13:28