觸控和滑鼠

首次重聚

簡介

近三十年來,桌上型電腦運算體驗一直以鍵盤和滑鼠或觸控板為主要使用者輸入裝置。不過,過去十年來,智慧型手機和平板電腦帶來了新的互動模式:觸控。隨著觸控式 Windows 8 電腦的推出,以及現在令人驚豔的觸控式 Chromebook Pixel 的發布,觸控功能現在已成為預期的桌面體驗的一部分。其中一項最大挑戰,就是打造不僅適用於觸控裝置和滑鼠裝置,也適用於使用者會同時使用這兩種輸入方式的裝置!

本文將說明瀏覽器如何內建觸控功能、如何將這個新介面機制整合到現有應用程式,以及觸控功能如何與滑鼠輸入搭配運作。

網路平台的觸控狀態

iPhone 是第一個在網路瀏覽器中內建專屬觸控 API 的熱門平台。其他幾家瀏覽器供應商也建立了類似的 API 介面,與 iOS 實作相容,現在由「觸控事件第 1 版」規格描述。Chrome 和 Firefox 桌面版、iOS 版 Safari、Android 版 Chrome 和 Android 瀏覽器,以及其他行動瀏覽器 (例如 Blackberry 瀏覽器) 都支援觸控事件。

我的同事 Boris Smus 撰寫了一篇很棒的 HTML5Rocks 觸控事件教學課程,如果您之前未曾接觸過觸控事件,這仍是很好的入門方式。事實上,如果您從未使用過觸控事件,請先閱讀該文章,再繼續操作。請繼續,我會等。

全部完成後,現在您已對觸控事件有基本瞭解,編寫觸控互動的挑戰在於,觸控互動可能與滑鼠 (以及模擬滑鼠的觸控板和軌跡球) 事件有很大不同。雖然觸控介面通常會嘗試模擬滑鼠,但這種模擬並不完美或完整;您確實需要處理這兩種互動樣式,且可能需要獨立支援每個介面。

最重要的是:使用者可能同時使用觸控和滑鼠

許多開發人員建構的網站會靜態偵測環境是否支援觸控事件,然後假設他們只需要支援觸控 (而非滑鼠) 事件。現在這個假設不成立,因為即使有觸控事件,也不代表使用者主要使用該觸控輸入裝置。Chromebook Pixel 和部分 Windows 8 筆電等裝置,現在同時支援滑鼠和觸控輸入法,未來會有更多裝置支援。在這些裝置上,使用者很自然會同時使用滑鼠和觸控螢幕與應用程式互動,因此「支援觸控」不等於「不需要滑鼠支援」。您不能將問題視為「我必須編寫兩種不同的互動樣式,並在兩者之間切換」,而是需要思考這兩種互動如何搭配運作,以及如何獨立運作。在 Chromebook Pixel 上,我經常使用觸控板,但也會伸手觸控螢幕。在同一個應用程式或頁面上,我會選擇當下最自然的互動方式。另一方面,有些觸控螢幕筆電使用者很少使用觸控螢幕,因此觸控輸入不應停用或妨礙滑鼠控制。

不過,我們很難得知使用者的瀏覽器環境是否支援觸控輸入。理想情況下,桌上型電腦的瀏覽器應一律支援觸控事件,以便隨時連接觸控螢幕顯示器 (例如透過 KVM 連接的觸控螢幕)。基於上述原因,應用程式不應嘗試在觸控和滑鼠之間切換,而是應該同時支援兩者!

同時支援滑鼠和觸控

#1 - 點擊和輕觸 -「自然」順序

第一個問題是,觸控介面通常會嘗試模擬滑鼠點擊,因為觸控介面必須在先前僅與滑鼠事件互動的應用程式上運作。無論使用者是用滑鼠點擊,還是用手指輕觸螢幕,系統都會繼續觸發「click」事件,因此您可以將其當做捷徑。不過,這個捷徑有幾個問題。

首先,設計更進階的觸控互動時,請務必小心:使用者使用滑鼠時,系統會透過點擊事件回應,但使用者觸控螢幕時,系統會同時發生觸控和點擊事件。單擊事件的順序如下:

  1. touchstart
  2. touchmove
  3. touchend
  4. 滑鼠游標懸停
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

當然,這表示如果您正在處理 touchstart 等觸控事件,請務必確保不會同時處理對應的 mousedown 和/或點擊事件。如果可以取消觸控事件 (在事件處理常式中呼叫 preventDefault()),系統就不會為觸控產生滑鼠事件。觸控處理常式最重要的規則之一是:

不過,這也會防止其他預設瀏覽器行為 (例如捲動),但通常您會在處理常式中完全處理觸控事件,而且您會想要停用預設動作。一般來說,您會想要處理並取消所有觸控事件,或避免為該事件設定處理常式。

其次,當使用者在行動裝置上輕觸網頁中的元素時,如果網頁並非專為行動裝置互動設計,則在 touchstart 事件和滑鼠事件 (mousedown) 處理之間,會有至少 300 毫秒的延遲。您可以使用 Chrome 進行測試,只要在 Chrome 開發人員工具中開啟「模擬觸控事件」,即可在非觸控系統上測試觸控介面!

這段延遲時間是為了讓瀏覽器有時間判斷使用者是否正在執行其他手勢,尤其是輕觸兩下縮放。顯然,如果您希望對手指觸控做出即時回應,這可能會造成問題。我們正在努力,嘗試限制自動發生這類延遲的情況。

Chrome for Android Android 瀏覽器 Android 版 Opera Mobile) Android 版 Firefox Safari iOS
不可縮放的可視區域 不延遲 300 毫秒 300 毫秒 不延遲 300 毫秒
無可視區域 300 毫秒 300 毫秒 300 毫秒 300 毫秒 300 毫秒

要避免這種延遲,最簡單的方法就是「告訴」行動瀏覽器,您的網頁不需要縮放,這可以透過固定可視區域來完成,例如在網頁中插入:

<meta name="viewport" content="width=device-width,user-scalable=no">

當然,這不一定合適,因為這會停用雙指撥動縮放功能,而這可能是無障礙功能所需,因此請盡量少用 (如果您確實停用了使用者縮放功能,建議您提供其他方式來提高應用程式中的文字可讀性)。此外,如果 Chrome 位於支援觸控功能的電腦級裝置上,且其他瀏覽器位於行動平台上,且頁面具有不可縮放的可視區域,則不適用此延遲。

#2:觸控不會觸發 Mousemove 事件

此時請務必注意,觸控介面中的滑鼠事件模擬通常不會延伸至模擬 mousemove 事件,因此如果您使用 mousemove 事件建構精美的滑鼠驅動控制項,除非您也特別新增 touchmove 處理常式,否則可能無法在觸控裝置上運作。

瀏覽器通常會自動為 HTML 控制項上的觸控互動實作適當的互動,因此,舉例來說,HTML5 範圍控制項在您使用觸控互動時就能正常運作。不過,如果您已導入自己的控制項,這些控制項可能無法在點擊和拖曳類型的互動上運作;事實上,某些常用的程式庫 (例如 jQueryUI) 尚未以這種方式原生支援觸控互動 (雖然對於 jQueryUI,這個問題有幾個猴子修補程式修正)。當我升級 Web Audio Playground 應用程式以支援觸控功能時,首先遇到的問題之一就是滑桿是以 jQueryUI 為基礎,因此無法與點擊和拖曳互動搭配使用。我改用 HTML5 Range 控制項,結果成功了。當然,我也可以直接新增 touchmove 處理常式來更新滑桿,但這樣做會有一個問題…

#3:Touchmove 和 MouseMove 並不相同

我發現有些開發人員會犯一個錯誤,就是讓 touchmove 和 mousemove 處理常式呼叫相同的程式碼路徑。這些事件的行為非常相似,但有細微差異,特別是觸控事件一律會以觸控開始的元素為目標,而滑鼠事件則會以滑鼠游標目前所在的元素為目標。這就是為什麼我們有 mouseover 和 mouseout 事件,但沒有對應的 touchover 和 touchout 事件,只有 touchend。

最常見的問題是,您不小心移除 (或重新定位) 使用者開始觸控的元素。舉例來說,假設圖片輪轉介面在整個輪轉介面上設有觸控處理常式,可支援自訂捲動行為。隨著可用圖片變更,您會移除部分 <img> 元素並新增其他元素。如果使用者開始觸碰其中一張圖片,而您隨後將其移除,您的處理常式 (位於 img 元素的祖系上) 就會停止接收觸控事件 (因為這些事件會分派到不再位於樹狀結構中的目標),看起來就像使用者將手指停留在同一個位置,即使他們可能已經移動並最終移除手指。

當然,您也可以避免在觸控動作進行時,移除具有 (或其祖系具有) 觸控處理常式的元素,藉此避免這個問題。或者,最佳做法是不要註冊靜態 touchend/touchmove 處理常式,而是等到收到 touchstart 事件後,再將 touchmove/touchend/touchcancel 處理常式新增至 touchstart 事件的目標 (並在結束/取消時移除)。這樣一來,即使目標元素遭到移動/移除,您仍可繼續接收觸控事件。您可以在這裡試試看:輕觸紅色方塊,然後按住 Escape 鍵,即可將其從 DOM 中移除。

#4:輕觸和 :hover

滑鼠游標隱喻將游標位置與主動選取分開,讓開發人員可以使用懸停狀態隱藏及顯示可能與使用者相關的資訊。不過,目前大多數觸控介面都無法偵測手指「懸停」在目標上方的動作,因此除非您也提供適合觸控操作的方式來存取這項資訊,否則請勿根據懸停動作提供語意上重要的資訊 (例如提供「這是什麼控制項?」彈出式視窗)。使用懸停功能向使用者傳達資訊時,請務必謹慎。

不過,有趣的是,在某些情況下,CSS :hover 虛擬類別可以由觸控介面觸發 - 輕觸元素會使其在手指按下時處於 :active 狀態,同時也會取得 :hover 狀態。(在 Internet Explorer 中,:hover 只有在使用者手指按下時才會生效,其他瀏覽器則會讓 :hover 保持生效,直到下一次輕觸或滑鼠移動為止)。這是讓彈出式選單在觸控介面上運作的好方法,讓元素處於啟用狀態的副作用是,系統也會套用 :hover 狀態。例如:

<style>
img ~ .content {
  display:none;
}

img:hover ~ .content {
  display:block;
}
</style>

<img src="/awesome.png">
<div class="content">This is an awesome picture of me</div>

輕觸其他元素後,該元素就不再處於啟用狀態,懸停狀態也會消失,就像使用者使用滑鼠游標並將其移出元素一樣。建議您將內容包裝在 <a> 元素中,使其成為定位點,這樣使用者就能透過滑鼠懸停或點擊、輕觸或按鍵切換額外資訊,不需使用 JavaScript。當我開始著手讓 Web Audio Playground 順利與觸控介面搭配運作時,我驚喜地發現,我的彈出式選單已經能順利在觸控介面上運作,因為我使用了這種結構!

上述方法適用於滑鼠游標介面和觸控介面。這與在懸停時使用「title」屬性相反,後者在元素啟用時「不會」顯示:

<img src="/awesome.png" title="this doesn't show up in touch">

#5:觸控與滑鼠精確度

雖然滑鼠在概念上與現實脫節,但事實證明滑鼠非常精確,因為基礎作業系統通常會追蹤游標的精確像素。另一方面,行動裝置開發人員也發現,手指觸控螢幕的精確度較低,主要是因為手指與螢幕接觸的表面積大小 (部分原因在於手指會遮住螢幕)。

許多個人和公司都對如何設計適合手指互動的應用程式和網站進行了廣泛的使用者研究,並出版了許多相關書籍。基本建議是增加邊框間距來提高觸控目標的大小,並增加元素之間的邊界,降低誤觸的可能性。(邊界不包含在觸控和點擊事件的命中偵測處理中,但邊框間距包含在內)。我對 Web Audio Playground 進行的主要修正之一,就是增加連接點的大小,以便更輕鬆地準確觸控。

許多處理觸控介面的瀏覽器供應商也將邏輯導入瀏覽器,協助在使用者觸控螢幕時指定正確的元素,並降低不正確點擊的可能性,雖然這通常只會更正點擊事件,而不會更正移動事件 (不過 Internet Explorer 似乎也會修改 mousedown/mousemove/mouseup 事件)。

#6:限制觸控處理常式,避免捲動時卡頓

此外,請務必將觸控處理常式限制在需要這些常式的元素上;觸控元素可能會耗用大量頻寬,因此請務必避免在捲動元素上使用觸控處理常式 (因為處理程序可能會干擾瀏覽器最佳化,導致無法快速流暢地捲動觸控畫面 - 新式瀏覽器會嘗試在 GPU 執行緒上捲動,但如果必須先透過 JavaScript 檢查每個觸控事件是否會由應用程式處理,就無法這麼做)。您可以查看這種行為的範例。

為避免這個問題,請遵循以下指引:如果您只在 UI 的一小部分處理觸控事件,請只在該處附加觸控處理常式 (例如,不要在頁面的 <body> 上附加);簡而言之,請盡可能限制觸控處理常式的範圍。

#7:多接觸點

最後一個有趣的挑戰是,雖然我們一直稱其為「觸控」使用者介面,但幾乎所有支援實際上都是多點觸控,也就是說,API 一次提供多個觸控輸入。開始在應用程式中支援觸控功能時,請考量多點觸控可能對應用程式造成的影響。

如果您建構的應用程式主要由滑鼠驅動,那麼您習慣使用最多一個游標點進行建構,系統通常不支援多個滑鼠游標。在許多應用程式中,您只需將觸控事件對應至單一游標介面,但我們看過的大多數電腦觸控輸入硬體,至少可以同時處理 2 個輸入,而大多數新硬體似乎至少支援 5 個同時輸入。如要開發螢幕鋼琴鍵盤,您當然會希望能夠支援多個同時觸控輸入。

目前實作的 W3C Touch API 沒有 API 可判斷硬體支援多少觸控點,因此您必須盡可能估算使用者需要多少觸控點,或留意實際看到的觸控點數量並進行調整。舉例來說,在鋼琴應用程式中,如果從未看到超過兩個觸控點,建議您新增一些「和弦」UI。PointerEvents API 確實有 API 可判斷裝置功能。

正在潤飾

希望本文能為您提供一些指引,協助您解決在導入觸控和滑鼠互動時常見的挑戰。當然,比任何其他建議更重要的是,您需要在行動裝置、平板電腦以及滑鼠和觸控式電腦環境中測試應用程式。如果沒有觸控和滑鼠硬體,可以使用 Chrome 的「模擬觸控事件」功能,測試不同情境。

只要遵循這些指引,就能輕鬆打造出引人入勝的互動體驗,無論是觸控輸入、滑鼠輸入,甚至是同時使用這兩種互動方式,都能順暢運作。