歡迎光臨
每天分享高質量文章

瀏覽器的輸入事件:除了點擊,我們如何做得更好?

作為接口開發者,對用戶輸入的響應可以說是我們工作的核心。為了搭建響應式的網絡應用,理解 touch、mouse、pointer 和 keyboard 動作與瀏覽器之間的關係是關鍵。你很有可能經歷過移動瀏覽器的三百毫秒延遲或者在觸摸移動中掙扎以及與滾動爭鬥。

在本文中我們將會介紹事件級聯,然後實現一個tap事件的demo,讓它支持多種輸入方式,同時又不會在代理瀏覽器(如Opera Mini)中崩潰。

概覽

如今,有三種主要的輸入方式用於與網頁交互:數字游標(滑鼠),觸碰(直接觸摸或用觸摸筆)和鍵盤。在javascript中,我們通過touch events, mouse events, pointer events 和 keyboard events去使用它們。本篇文章里,雖然一些事件有標準的基於鍵盤的交互,比如我們主要關註基於touch(touch-based)和基於mouse的(mouse-based)交互,比如click和submit事件。

你很可能已經實現過touch和mouse事件的事件處理器。在不久之前有過這麼一段時間,類似於這種寫法受到推崇:

/** 永遠不要這麼做!*/

$(‘a’, (‘ontouchstart’ in window) ? ‘touchend’ : ‘click’, handler);

微軟首開先河,通過“指標事件(Pointer Events)”規範創造了一個更好的面向未來的事件模型。指標事件是一個目前W3C推薦的抽象輸入機制。它向用戶提供用戶代理(user agent)靈活性去改寫在一個事件系統中無數的輸入機制。滑鼠、觸摸以及觸控筆是我們目前很容易聯想到的例子,雖然Myo(手勢控制臂環)和Ring(智能指環)這樣的延伸非常的有想象力。Web開發者們似乎對此很興奮,但並不是所有瀏覽器工程師都有同樣的感覺。因為Apple和Google已經同時決定不去實現指標事件了。

Google目前的決定並不一定是不變的,但是他們並沒有在指標事件上有任何活躍的動作。通過polyfill和其他的解決方案,我們的輸入以及對指標事件的使用最終將會是令天平傾斜的因素之一。Apple在2012年對指標事件發表過宣告,至今我也沒有從其他Safari的工程師那聽到任何公開回應。

The Event Cascade 事件級聯

當用戶在移動設備上輕敲一個元素時,瀏覽器觸發了一系列的事件。這一系列事件通常是這樣的:

touchstart → touchend → mouseover →mousemove → mousedown → mouseup → click

由於web的向後兼容性。指標事件使用另一種方式觸發兼容事件:

mousemove → pointerover → mouseover → pointerdown →mousedown → gotpointercapture → pointerup → mouseup → lostpointercapture → pointerout→ mouseout → focus → click

事件規範允許UA在兼容事件的實現上有所不同。Patrick Lauke和Peter-Paul Koch維護著很多關於這個話題的很多參考資料,在文章最後可以找到這些資源的鏈接。

下麵的圖片展示了下列動作的事件級聯:

  1. 開始點擊一個元素一次,
  2. 再次點擊一個元素,
  3. 移開元素

請註意:該事件棧有意忽略掉focus 和 blur。

iOS設備上的“點擊一個元素兩次並移開”事件級聯(圖片: Stephen Davis) (大圖)

安卓4.4設備上的“點擊一個元素兩次並移開”事件級聯。 (圖片: Stephen Davis) (大圖)

IE 11瀏覽器(在兼容觸碰事件實現之前的版本)上的“點擊一個元素兩次並移開”事件級聯。 (圖片:Stephen Davis) (大圖)

事件級聯的應用

現如今,由於瀏覽器工程師並不是很努力,大部分桌面網站僅僅只是“能運行”而已。儘管該級聯看起來很粗糙,但我們之前建立的滑鼠事件的保守方法通常還是有效的。

當然了,這裡還是有問題。聲名狼藉的300秒延遲是其中最出名的問題,但是scrolling, touchmove 和 pointermove事件之間的互相影響以及瀏覽器繪圖也很讓人頭疼。

  • 我們優化了在Android和桌面上的新版的Chrome,使用到了啟髮式方法,例如用

    如果我們的目的是建立一個基於本地平臺的用戶體驗和優化的網絡應用,那麼我們需要降低交互響應的延遲。為了做到這一點,我們需要使用原始事件(down, move 和up)並且建立我們自己的組合事件(click, double-click)。當然了,我們仍然需要將回退處理程式包含在內,以便我們的應用有更大的兼容性和可用性。

    實現這些需要不少的代碼和知識。為了避免瀏覽器遇到300毫秒(或其他的時長)延遲,我們需要自己去處理整個交互的生命周期。對於給定的{type}down事件,我們需要將完成該動作的所需的所有事件都系結起來。當交互完成,我們還得去做善後工作,將所有開始的事件解綁。

    作為網站開發者的你,是唯一知道頁面是該縮放還是需要等待其他雙擊事件的人。當且僅當你需要推遲一個回呼時,你才會去允許一個有意的延遲。

    在下麵的鏈接中,你會看到一個無依賴的點擊的小demo,用於展示如何去實現一個多輸入,低延遲的點擊事件。

    明確地說,利用這個簡陋的demo去實現你的方案是個壞主意。下麵的代碼實現僅僅用於教學目的,不應該在實際應用中使用。產品級的解決方案是存在的,比如:FastClick, polymer-gestures 和 Hammer.js.

    重要的部分

    一切都要從系結你初始的事件處理器開始。下麵的樣式被認為是一種萬無一失的處理多設備輸入的處理方式。

    /**

    *

    * 如果有指標事件,讓平臺去處理輸入(機制的抽象)???。

    * 如果沒有的話,那麼就該你自己去處理mouse和touch事件。

    *

    */

    if (hasPointer) {

    tappable.addEventListener(POINTER_DOWN, tapStart, false);

    clickable.addEventListener(POINTER_DOWN, clickStart, false);

    }

    else {

    tappable.addEventListener(‘mousedown’, tapStart, false);

    clickable.addEventListener(‘mousedown’, clickStart, false);

    if (hasTouch) {

    tappable.addEventListener(‘touchstart’, tapStart, false);

    clickable.addEventListener(‘touchstart’, clickStart, false);

    }

    }

    clickable.addEventListener(‘click’, clickEnd, false);

    系結touch事件處理器可能會導致繪圖性能下降,即使它們不做任何事情。為了降低這種影響,我們常常推薦在開始的事件處理器中系結跟蹤事件。不要忘記了做好事後的清理,在你的完成動作的處理器中解綁跟蹤事件。

    /**

    * 在tapStart中,我們想要系結我們的move和end事件去探測

    * 這是否是一個“tap”動作。

    * @param {Event} 瀏覽器事件物件

    */

    function tapStart(event) {

    // 系結跟蹤事件。“bindEventsFor”可以根據我們目前的

    // 事件型別去系結合適的pointer, touch或mouse事件。

    // 另外,它省了事件標的,讓我們對指標事件的

    // “setPointerCapture”方法有相似的行為。

    bindEventsFor(event.type, event.target);

    if (typeof event.setPointerCapture === ‘function’) {

    event.currentTarget.setPointerCapture(event.pointerId);

    }

    // 防止級聯

    event.preventDefault();

    // 啟動分析器去跟蹤事件之間的時間。

    set(event, ‘tapStart’, Date.now());

    }

    /**

    *

    * 我們的工作在這結束。讓我們清理我們的跟蹤事件。

    * @param {Element} 指定html元素

    * @param {Event} 瀏覽器事件物件

    */

    function tapEnd(target, event) {

    unbindEventsFor(event.type, target);

    var _id = idFor(event);

    log(‘Tap’, diff(get(_id, ‘tapStart’), Date.now()));

    setTimeout(function() {

    delete events[_id];

    });

    }

    剩下的代碼不言自明。實際上,有很多東西需要補充。實現定製的手勢需要你深度利用瀏覽器的事件系統。為了讓你遠離煩惱,請不要在你的代碼庫中重覆做這些事,而應該建立或使用一個強有力的庫,比如Hammer.js, Pointer Events(一個jQuery polyfill) 或polymer-gestures。

    總結

    一些曾經定義非常清晰的事件現在充滿了歧義。click事件以前只有一個定義,但是觸摸屏的出現將它複雜化了。現在還需要關註動作是否是雙擊,滾動事件或其他系統級的手勢。

    好訊息是我們現在理解了事件級聯和用戶行為和瀏覽器響應之間的互相作用。有了對原始事件的理解,我們可以在自己的專案中為用戶以及網站的未來做出更好的選擇。

    在建立支持多種設備的網站時,你遇到了哪些始料不及的問題?為了應對你的網站中無數的交互模型,你又使用了哪些手段呢?

    附加資源

    • “Pointer Events Finalized, But Apple’s Lack of Support Still a Deal Breaker,” Peter Bright
    • “Getting Touchy: An Introduction to Touch and Pointer Events,” including slides and talk, Patrick E. Lauke
    • “Apple’s Web?” by Tim Kadlec
    • “Avoiding the 300ms Click Delay, Accessibly,” Tim Kadlec
    • “Touch Table,” Peter-Paul Koch
    • “Making the Web ‘Just Work’ With Any Input: Mouse, Touch, and Pointer Events,” Jacob Rossi
    • FastClick library
    • Hammer.js
    • polymer-gestures
    • PointerEvents jQuery polyfill
    • “Implement Custom Gestures,” Google Developers

    原文出處:www.smashingmagazine.com

    譯文出處:伯樂在線 – 鴨梨山大

    譯文鏈接:http://web.jobbole.com/82445/

赞(0)

分享創造快樂