偵測觸碰事件:重點在「原因」而非「方法」─ 上篇

作者:
瀏覽:266

說到要讓網頁或 App 能在行動裝置上更「友善」的重點功能或介面,大家應該都認為是觸控式螢幕。而開發人員最常提出的問題大概就是「我要如何偵測到觸控功能的裝置」?

觸控的偵測功能

雖然以前的解決方案仍有專利與不相容方面的問題 (如 Mozilla 實驗性質,由供應商預先設定的事件模型),但目前大多數瀏覽器均已建構了相同的觸碰事件 (Touch Event) 模型。該解決方案首先由 Apple for iOS Safari 所提出,其他瀏覽器隨之接著採用,最後回歸成為 W3C 的基礎規格。

因此,不論是否要以程式設計的方式進行偵測,瀏覽器均可透過簡單的偵測功能,支援互動的觸控作業:

if ('ontouchstart' in window) { /* browser with Touch Events running on touch-capable device */ }

此編碼片段可於目前的瀏覽器中穩定運作。但若要用於較舊版本的瀏覽器,就必須跳脫多樣不同的偵測功能框架而另外設定。若你的應用是針對舊版瀏覽器所設計,這裡建議你應該先參閱 Modernizr,特別是其中所提到不同的觸控測試方法,應能讓你順利解決相關問題。

我上面強調過「大多數瀏覽器」可支援此觸控事件模型。而比較重要的例外就是 Internet Explorer 瀏覽器。最高到 IE9 都尚未支援初階的觸控功能,但 IE10 則開始支援 Microsoft 自己的 Pointer Events。此事件模型已經提交到 W3C 的標準化作業,目的是要能透過新類別的單一事件,統合如滑鼠、觸控筆、觸控板等的「指示器 (Pointer)」裝置。由於此模型設計之初,並未納入任何獨立的「觸控」,因此其本身並無法使用 ontouchstart 的偵測功能。若要使用 Pointer Events,則建議在「觸控啟動式 (Touch-enabled)」的裝置上執行瀏覽器。因為 Pointer Events 並不會檢查物體是否存在,亦不會回傳 navigator.maxTouchPoints (另請注意,Microsoft 的 Pointer Events 目前仍為預先設定的狀態,所以我們實務上仍期待能採納 navigator.msMaxTouchPoints)。若物體存在且回傳的值大於零,即可支援觸控功能。

if (navigator.msMaxTouchPoints > 0) { /* IE with pointer events running on touch-capable device */ }

將這段加入之前的偵測功能 (內含 Pointer Events 未預先設定的版本,以因應未來所需的相容性),接著應能得到合理且簡潔的程式碼片段:

if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { /* browser with either Touch Events of Pointer Events running on touch-capable device */ } 觸控偵測的使用方式

現在已有許多常見技術可利用偵測功能,進而最佳化觸控功能。偵測觸控最常見使用範例,就是提高觸控介面的反應度。

在使用觸控式螢幕介面時,瀏覽器會在「觸碰的動作 (如點選鏈結或按鈕)」與「時間內實際發生的點選事件」之間,自行產生約 300 ms 內的人工延遲。

另特別一提,若是支援 Touch Events 的瀏覽器,則會在「touchend」與「瀏覽器模擬的滑鼠事件」之間產生延遲,以相容於 mouse-centric 指令碼:

touchstart > [touchmove]+ > touchend > delay > mousemove > mousedown > mouseup > click

請參閱「事件接收器 (Listener)」測試頁面,以了解事件啟動的順序,還有 GitHub 上可用的程式碼

這種延遲機制,可避免使用者意外啟動其他頁面元素,而完成如頁面縮放的雙點觸控事件。

有趣的是,Android 版本的 Firefox 與 Chrome 均可針對固定、無法縮放視點的頁面,而自行移除此延遲機制。

另請參閱「事件接收器 (Listener) 搭配 user-scalable=no」測試頁面,還有 GitHub 上的可用程式碼

另有某些討論提到 Chrome 於其他情況下的進一步行為,可參閱 issue 169642 in the Chromium bug tracker
雖然此種延遲機制為必要功能,但也能拖慢 Web App 或不產生任何反應。我們這裡使用常見的小把戲來支援觸控,且只要發生觸碰事件亦可直接反應 (可為 touchstart ─ 使用者觸碰螢幕;亦可為 touchend ─ 使用者手指離開螢幕),進而可取代傳統的 click:

/* if touch supported, listen to 'touchend', otherwise 'click' */ var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click'); blah.addEventListener(clickEvent, function() { ... });

雖然此最佳化的方式已經廣泛使用,但仍是以明顯的邏輯謬誤為基礎而成。
這種人工延遲同樣出現在 Pointer Events 的瀏覽器中。

pointerover > mouseover > pointerdown > mousedown > pointermove > mousemove > pointerup > mouseup > pointerout > mouseout > delay > click

雖然上述的最佳化方式可進一步延伸,用以檢查 navigator.maxTouchPoints 之後,再將 Listener 銜接 pointerup (而非 click),但其實還有更簡單的方法。就是把我們元素裡的 touch-action CSS 屬性設定為 none,將延遲降到最低。

/* suppress default touch action like double-tap zoom */ a, button { -ms-touch-action: none; touch-action: none; }

可參閱 「事件接收器 (Listener) 搭配 touch-action:none」測試頁面,還有 GitHub 上的可用程式碼

錯誤的假設

必須特別注意的是,這些根據「觸碰有效性」而成的最佳化類型,其實有其根本上的缺陷。相關的最佳化作業是假設「使用者根據裝置功能而做出動作」。更明確的說:上述範例均假設「裝置具備觸控輸入的功能,且使用者的觸控方式也確實符合唯一的互動方式」。

如果只有典型的「行動電話」與「平板電腦」具備觸控輸入的功能,那上面的假設確實有其立足點;因為觸控式面板是唯一的輸入方式。但近來出現的全新裝置,往往具備傳統筆記型/桌上型電腦的規格 (包含滑鼠、鍵盤、軌跡板),卻又同時搭載觸控式螢幕,如 Windows 8 電腦Google 的 Chromebook Pixel

題外話,即使是行動電話或平板電腦,在某些平台上亦可讓使用者添加更多輸入裝置。對 iOS 來說,iPhone/iPad 僅能配對藍牙鍵盤而達到單純的文字輸入;Android 與 Blackberry OS 讓使用者外接滑鼠。

在 Android 系統上,這種滑鼠其實比較像「觸碰」,可平均放出「觸碰事件」與「模擬滑鼠事件」的相同序列,且中間仍保有延遲機制。也因此,上述的最佳化範例可運作無虞。但 Blackberry OS 就單純只會放出滑鼠事件,因而可能造成下面將提到的某些問題。

開發人員已經逐漸發現了此一變化:觸控功能已經不再侷限於「行動裝置」。不論使用者是否選擇觸控功能成為主要/次要的輸入方式,裝置幾乎都必須搭載觸控功能。其實在互動的過程中,使用者可能隨時轉換輸入方式。

上面所提到的程式碼片段,就確實呼應了這種新型態裝置的後續效應。對使用 Touch Events 的瀏覽器來說:

var clickEvent = ('ontouchstart' in window ? 'touchend' : 'click');

其實就代表「如果裝置支援觸控功能,就只接收 touchend 而不接收 click」。若用於多重輸入的裝置,就直接切斷了滑鼠、鍵盤、觸控板的擴充機會。

 

── 待續 ──

 

原文出處:https://hacks.mozilla.org/2013/04/detecting-touch-its-the-why-not-the-how/