/ Touch Events

[Mobile] 觸控事件一二三

由於觸控式的手機流行,加上 iPhone 盛行,Android 後起之秀持續攪亂一池春水,所以手機網頁設計突然變得很熱門。所以最近 Responsive Web Design 也突然流行起來,翻成響應式網頁設計感覺很怪,我個人會叫他適用性網頁設計,畢竟,他是針對不同的裝置,所使用的不同的螢幕尺寸,而去回應不同的畫面結果,所以適用性或許比較容易理解(吧?

好,其實無關設計,我們來聊一下觸控事件。


w3c 的規範裡頭,觸控事件有下列幾種,

  • touchstart
  • touchmove
  • touchend
  • touchcancel

那,w3c 是怎麼解釋這些動作的呢?

5.3 List of TouchEvent types

touchstart

其實他沒有預設動作,只是單純偵測觸控開始這個事件而已。需要注意的地方,就如同官方所述,

If the preventDefault method is called on this event, it should prevent any default actions caused by any touch events associated with the same active touch point, including mouse events or scrolling.

阿鬼你還是說中文吧

意思約莫是,倘若你在 touchstart 事件中呼叫了 preventDefault 方法,你必須自己去避免相同的元件上,再次被觸控的可能。因為再次被觸控的話,你的事件會被再次呼叫,但,不是呼叫 touchstart,而可能會去叫用 touchmove,倘若你有綁定這個事件的話。

聽起來很玄妙,直接貼出實際例子可能比較好理解,

Touch Event Sample

第一階段,是按一下螢幕,他便會取得 touchstarttouchend 兩個事件狀態。而第二階段,出現了 touchmove 的連續動作,操作模式是,按著螢幕,另外一隻手指再次的按下同樣區塊,他就會變成 touchmove 的事件命令,最後手指離開才觸發 touchend

那我不要呼叫 preventDefault 總會沒事吧?嗯,其實並不會。不管你有沒有叫用 preventDefault 方法,觸控事件還是會像上述的動作一樣的發生,因為裝置本身就允許你使用多點觸控(在 Safari 中限制 2 點,超過會無效)。

所以你是否要避免事件不小心進入了 touchmove 的狀態,你就得自己想辦法來避開這些事情了。

touchmove

w3c 也提出了跟 touchstart 一樣的說法,關於 preventDefault 之類的事情,只是我想不會有人想把捲軸事件跟觸控的 DND 事件混在一起寫,所以我就不引述他的說明了。

而令人感到最神奇的地方是,倘若呼叫了 touchstart 事件,然後不使用 touchend 事件,那麼,在那個區塊的元件,除了第一次會觸發 touchstart 之外,往後永遠都會觸發 touchmove,即便你手指離開觸控裝置,再一次按下,他還是會去觸發 touchmove,這一點需要留意。

且,另外一個神奇的地方,當你使用一個全域,或是物件來儲存 touchstart 的事件資訊時,由於參考(reference)的關係,touchmove 的資訊會去改寫你在 touchstart 所儲存的資訊。舉個例子來說,

var touchmove = function() {};

touchmove.prototype = {
    initDND: {},
    moveDND: {},

    _startTouch: function touchmove_start_touch(event) {
        var touches = event.originalEvent.touches ||
            event.originalEvent.targetTouches ||
            event.originalEvent.changedTouches;

        this.initDND = $.extend({}, touches);
        console.log(this.initDND, touches);
    },
    _moveTouch: function touchmove_move_touch(event) {
        var touches = event.originalEvent.touches ||
            event.originalEvent.targetTouches ||
            event.originalEvent.changedTouches;

        this.moveDND = $.extend({}, touches);
        console.log(this.initDND, this.moveDND, touches);
    },
    _endTouch: function touchmove_end_touch(event) {
        console.log(this.initDND, this.moveDND, touches);
    },

    init: function touchmove_init() {
        var instance = this;

        $('#elem')
        .bind('touchstart', function(event) {
            event.preventDefault();
            instance._startTouch(event);
        }, false)
        .bind('touchmove', function(event) {
            event.preventDefault();
            instance._moveTouch(event);
        }, false)
        .bind('touchend', function(event) {
            event.preventDefault();
            instance._endTouch(event);
        }, false);
    }
};

你會發現,在 touchmove 的事件中,touches 輸出的數值竟然一模一樣,然而,最後 touchend 輸出的數值竟然卻又不一樣了!?

看到鬼?

是的,差不多是這樣。觸控事件有一件很詭譎的事情,我今天剛好有遇到,你們也剛好有來看,那就姑且聽之,

  • touchstart 觸發一個 Event,輸出了一組資訊
  • touchmove 其實並沒有重新觸發一組新的 Event,而是 touchstart 的延續動作
  • touchend 又觸發了一個新的 Event,輸出一組資訊

所以,當我們把 touchstart 的資訊儲存起來,然後交給 touchmove 去做處理(運算爾等),你可以透過 console.log 發現,他們兩個 Event 所給出來的數值永遠都是一模一樣的,然後,我們所儲存的會跟你當下抓到的不同!

至於 touchend 我們容後再述。

怎麼避開這種問題?其實也不難,既然我知道 Event 事件資訊會被 touchmove 不斷地改寫,那我只要不使用事件預設的屬性值來紀錄即可。例如說我們上述的儲存容器,使用 $.extend() 的方式將他丟到外面去就好。這樣就可以避開參考的問題,讓你所取得的事件回傳資訊是你所想要的。我們其實不能說他回傳錯誤,這只是觸控的一個特性而已。

touchend, touchcancel

首先要說明一件事情,沒有 touchstart 就不會有 touchend,當然也不會有 touchmove。這個事件其實跟 touchstart 一樣也沒什麼好說的。而,一樣令人弔詭的一件事情,就是跟 touchmove 一樣,他的 Event 資訊不會改寫任何東西,因為他的 TouchList 跟上面的不一樣

TouchList 不一樣是哪招?

是的,就像是 w3c 所描述的一樣,

TL; DR

要取得 touchend 或是 touchcancel 需要針對 changedTouches 這項屬性質來取得,這是唯一的區別。那你說 touchstarttouchmove 會不會有 changedTouches 呢?是的,也會有。那我不就可以三種事件都取用 changedTouches 呢?當然不行

為什麼不行?

因為數值會一樣!會一樣!會一樣!會一樣!會一樣!會一樣!

至於 touchcancel 一直以來都是一個謎團。根據 Safari Web Content Gride 的說明,有寫跟沒有寫一樣 所以我就不引述了。倒是有一個方法可以強制做出 touchcancel 的動作,

  • 開啟 iPhone/Safari,進入你的網頁
  • 按下 touchstart 事件的區塊
  • 按下實體 Home 鍵

看起來蠻蠢的,但是我這樣作,的確會觸發 touchcancel 這個事件。

所以,大抵上來看,touchcancel 這個事件,是由裝置或是裝置內的應用程式,例如通知(Notification),所觸發的干擾,而瀏覽器在無法正確接受的情況下,會觸發 touchcancel 的事件。不過現在 iOS 已經把通知改為畫面上端顯示,並不會佔用螢幕,所以其實瀏覽器並不會受到干擾。

結語

沒事不要半夜測試 Touch,會被女友揍,早點上床睡覺比較好!