聊聊各端手勢體系以及對 Web 標準手勢的思考

「北海 Kraken」是一款基於 Flutter 的 Web 渲染引擎,經過基於 W3C 標準來開發實現前端開發者經常使用的能力。 Kraken 團隊也積極探索定義新的問題以及能力,指望經過參推進標準定製的方式讓 Web 技術變得更好。 歡迎你們關注 「北海 Kraken」: http://openkraken.com/javascript

在過去,早期的 Web 更多用作內容展現的頁面,最先從後端框架中直出,再配上各類 CSS 以及 JS 的交互內容,以完成最終對頁面內容的展現,那時候的 Web 更多屬於 【內容開發】,作內容的直出與展現。而現在現代 Web 開發體系已經有了翻天覆地的變化,早已超出了【內容開發】的範疇,在各個領域都有 JavaScript 的身影。一樣,Web 也已經脫離了客戶端以及瀏覽器的限制,各式各樣基於 Web 標準或者私有標準的 Web runtime 層出不窮。【Web 應用開發】區別於傳統的【內容開發】,它對開發者提出了更高的要求,也對 Web 的能力提出了更高的要求,不管是基於標準化方面的考量,仍是基於對易用性的考慮,咱們都指望 Web 開發者能夠得到經過更高級的封裝的標準的高性能的能力。前端

而手勢能力就是其中的一塊。java

目前在 Web 標準中,手勢能力是屬於缺失的一塊能力,更多的開發者經過 hammer.js 來得到一個經過 JavaScript 模擬出來的手勢事件來開發一個手勢強交互的應用,或者是直接基於更底層的 Touch event來作進一步的封裝。git

可是不管是類 hammer.js 的前端手勢方案,仍是 Touch event的封裝,都會致使一些問題,我將從易用性、性能、標準化的角度來作進一步的分析:github

  • 易用性: 開發者必須手動去實現或者封裝更高一級的手勢能力,沒法直接從 element 上得到某個高級手勢的 event 事件。不管是開發成本,都須要額外加載或者執行額外的 CDN,都是對前端資源的一種損耗。
  • 性能: 經過 JavaScript 實現的方案須要頻繁地經過 Bridge 將手勢的能力傳遞到前端,而後再去計算模擬相關的手勢事件。頻繁的傳遞數據增長了 Bridge 的消耗,不斷執行的 JavaScript 會阻塞 UI 線程,若是須要更強大的手勢能力支撐,咱們必須進一步封裝【競爭場】等能力的實現來達到手勢競爭的目的,而這部分能力本應下沉到渲染引擎自己,而不是在 JavaScript 中處理。
  • 標準化: 各個開發者實現的標準不統一,判斷的基準不一致,透出的 event 能力不對齊會致使各個平臺甚至到各個頁面的標準不統一。譬如說在同一個 iOS 設備上訪問兩個不一樣開發者開發的頁面,不統一的手勢能力可能會給用戶帶來及其糟糕的體驗。非標準化的手勢能力在各個端上也顯得格外突出,下面我介紹各端的手勢能力時會介紹這些差別點。

連續手勢與離散手勢

首先我須要介紹一下連續手勢與離散手勢的概念,以便讀者能夠更好地區分這兩種手勢的不一樣,以及瞭解實現不一樣的手勢能力對開發、性能、易用性等緯度的影響。小程序

首先,咱們須要知道,因爲在端側有各類各樣的屏幕操做的設備,常見的好比說類 apple pencil 的電子筆設備(pen),手指直接觸摸操做(touch),還有鼠標(mouse)等。因此在 W3C 標準中, 將全部的接觸屏幕的物理設備抽象成了一個 pointer,不管上層是那種物理設備,對於屏幕只感知與抽象這一個觸摸到的點,基於 type 區分具體的上層的物理設備。後端

pointer

一個完整的手勢包含了手指開始接觸屏幕(pointer down),而後手指在屏幕上進行偏移(pointer move),以及手指擡起離開屏幕(pointer up),暫時不討論 cancel、out 等狀況。固然,其中中間 pointer move 的過程是能夠省略的,最多見的省略 pointer move 的手勢譬如說 click 或者 long press 等(固然,若是點擊設備不是一個鼠標而是一根手指,其實手指實際接觸是確定會產生輕微移動狀況的,譬如在 FLutter 中,容許這個細微的移動距離在 18 個像素點內,即視爲不移動)。能夠預見的是將來會有更多的物理設備操做屏(甚至不是屏),基於底層觸摸點的 pointer 抽象有利於上層作更多的擴展。瀏覽器

瞭解了這些,接下來咱們來了解一下連續手勢與離散手勢的差別。app

  • 連續手勢:從 pointer down 到 pointer moves 到 pointer up,中間過程能夠經過 state 狀態來描述的手勢,能夠清楚地經過不一樣的回調或者不一樣的狀態讓開發者感知目前手勢所處的狀態的手勢。常見連續手勢:pan。
  • 離散手勢:完整手勢觸發完畢後纔會經過回調來通知開發者,無中間狀態的轉換。常見離散手勢:click。

連續手勢會頻繁經過回調或者狀態來通知開發者目前手勢所處的狀態,咱們來看一種狀況:框架

element.addEventLisenter('pan', (gestureEvent) => {
	if (gestureEvent.state === 'up') {
		// do something...
	}
})

假設咱們須要在 Web 標準中實現 pan 這個手勢,若是它是一個連續手勢,而咱們的場景只須要用 up 這種狀態,就須要不斷地將當前的狀態經過 Bridge 以及 JS engine 傳遞到 JavaScript 中,這頻繁的傳遞開銷是對設備性能的一種浪費。固然,也有框架方案經過更加細分的粒度去解決這個事情,譬如說拆分紅 panstartpanupdatepanend等,當開發者不給這些方法註冊回調時,能夠在框架內部判斷並作相應優化。然而細分的 API 抽象不夠底層,對於開發者來講也並不那麼友好。

而對於離散手勢,咱們則不須要考慮手勢過程當中的狀態傳遞,只須要把最終的結果返回給開發者便可,離散手勢屏蔽了許多內部處理的細節,保證了開發者註冊的回調只能完整的手勢操做完之後才能被命中。有效地下降了連續手勢數據的傳遞量。可是相較於連續手勢,離散手勢的缺點是開發者沒法很好地感知中間狀態。

接下來咱們來看一下各個端上實現的手勢體系、優缺點以及差別性。

各端手勢體系

hammer.js

hammer

hammer.js 做爲一個前端實現的 gesture lib,經過註冊 Touch 事件作封裝來完成具體的操做的判斷,在前端作手勢的方案在前面已經提過,須要不斷地經過 Bridge 以及 JS engine 傳遞到 JavaScript 中,而後才能最終在 JavaScript 中處理手勢操做,只要有操做就會被拋到 JavaScript 中進行處理,頻繁的傳遞耗費了許多沒必要要的性能。咱們更但願這部分能力能夠下沉到渲染引擎自己,這樣能夠節省很是多沒必要要的數據傳遞開銷。

若是在基礎手勢判斷之上想進一步引入更加複雜的【競技場】等能力,這部分會使得 JavaScript 中的邏輯更加複雜,即使拋開「能不能」在前端作相關實現來講,過多的 JavaScript 運行佔用計算資源也是咱們並不想要的。

同時,須要單獨引入一個 CDN 腳原本支持相關的功能,對於包體積以及首屏也增長了額外的成本。可是又考慮到自己瀏覽器並不自帶這些功能,通常開發者也沒法很好地將這套方案優化並下沉到瀏覽器中,因此在反而在大部分前端業務場景成爲來較優的技術選型。

Flutter

Flutter

  • Tap
  • Double tap
  • Long press (500 ms 以上的長按)
  • Vertical(Horizontal) drag 橫(縱)滑 在 drag 上作了進一步封裝,在 x 軸或者 y 軸偏移超過最小距離並達到閾值速度可觸發。
  • scale scale 會包含放大縮小以及旋轉的手勢,至關於其餘端中的 Pinch + Rotation
  • Pan Pan 內部實現,須要達到一個最小速度以及最小移動距離的 drag 才能觸發 Pan,Pan 是基於 drag 之上的封裝,增長了判斷。Flutter 內置一個 pointer down + pointer moves + pointer up 只能觸發一次手勢,因此 Pan 只能觸發一次(與 hammer 不一樣,爲有狀態手勢) 詳見: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/monodrag.dart#L579

Flutter 的手勢體系除了 Long press 均爲連續手勢,無離散手勢,Tap 也會經過 TapDown、TapUp 等狀態來完成。每一箇中間狀態會經過不一樣的回調函數來支持開發者處理邏輯,返回參數也根據中間狀態的不一樣而不一樣。排除 Widget 的統一封裝來看,跟安卓相似,回調過多,且返回參數不統一,不利於標準化。可是對於內部的手勢回調來講,細分的接口各司其職,傳遞所需的參數都是必須的,開發者能夠直接獲取具體的返回信息。

iOS

  • Tap(離散手勢,100 ms 左右的點擊行爲)
  • Long Press (連續手勢,500 ms 以上的點擊行爲)
  • Pan (連續手勢,平移,相似 drag,可是能夠在移動過程當中不斷變化方向)
  • Swipe (離散手勢)
  • Pinch(連續手勢,向外捏時放大,向內捏時縮小)
  • Rotation(連續手勢,旋轉)

iOS手勢

爲了方便你們瞭解各個手勢的區別,尤爲是 Pan 跟 Swipe 的區別,特意放上了iOS 開發者文檔的一些圖片

iOS 的手勢能夠帶上多個 touch pointer,同時知足了幾個手指操做的能力。好比三指滑動(三根手指的swipe)、雙指點擊(兩根手指的 Tap)等。它提供了開發者對某一個手勢處理成一個註冊回調函數,經過 state 判斷目前手勢的狀態。離散手勢與連續手勢共存。

Android

  • View 上直接提供 click 以及 touch 的一些方法
    • OnDragListener:拖動事件。
    • OnLongClickListener:長按擡起時的事件。
  • GestureDetector.OnGestureListener
    • onDown:手勢識別器的 down 事件。
    • onFling: 相似 swipe。
    • onLongPress: 長按。
    • onScroll:scroll view 滾動時的事件。
    • onShowPress:按下後沒擡起,至關於(up、move、down 的中間 move 狀態,只是沒move)。
    • onSingleTapUp: 點擊擡起,對應 onDown。
  • GestureDetector.OnDoubleTapListener: 雙擊。
  • ScaleGestureDetector:旋轉,捏,分 begin、onScale、end。

相對來講,Android 的手勢體系比較細分,大體上跟 FLutter 比較像,可是 Flutter 是不一樣手勢在不一樣類中的,Flutter 基本上都是離散手勢,安卓不少連續手勢,可是更加細分。

標準

綜上分析了 Flutter、iOS、Android 以及一個前端實現的 gesture lib(hammer.js),不難發現,每一個端實現的手勢方案都大同小異。無非都實現了這幾種方法: click(Tap)、swipe、Pan、Long Press (Press)、Pinch 與 Rotation(或者 Scale)。可是各個平臺對每一個手勢的實現仍是有些許的差別,不管是具體手勢的代碼邏輯判斷仍是具體手勢的拆分或者命名,均有不一樣。

那在 Web 技術上,咱們應該使用怎麼樣一套手勢規範,來兼顧易用性、性能以及標準化呢?就目前來看,基於 Web 技術體系發展來的 Web runtime 的已經很是多了,諸如 Web、React Native、小程序等體系已經在端側帶來了巨大的運行時碎片化。將來不止於移動端上,還有各類 IOT 設備出現,可能會有愈來愈多的 Web runtime 會出現。將來可能會有更多領域會有不同的終端設備,而摺疊屏、柔性屏的到來也可能會讓端側的設備(手機、IOT、車載等)造成更多更復雜的的跨端場景,隨之而來的也是更多的交互手勢來與這些設備進行「溝通交流」。

很遺憾的是目前 W3C 上沒有相應的手勢規範,咱們更指望有一個統一的既定標準來規範,咱們也在 W3C 中文興趣小組上發起了一個討論,目前此討論已經提到了 UIEvent。咱們指望經過易用性、性能以及標準化這幾個緯度去討論手勢規範以及對應的手勢標準化能力的必要性,以及最終推進規範創建的可行性,也歡迎更多的小夥伴加入該討論。

此外,該標準提案目前已經在 北海 Kraken 上實現,開發者能夠直接使用 加強的手勢能力 來開發複雜的交互應用。後續 北海 Kraken 團隊將會在複雜的業務場景上定義出更多問題以及通用能力,指望能夠經過參與推進標準定製的方式讓 Web 技術變得更好。

相關文章
相關標籤/搜索