由於觸控式的手機流行,加上 iPhone 盛行,Android 後起之秀持續攪亂一池春水,所以手機網頁設計突然變得很熱門。所以最近 Responsive Web Design 也突然流行起來,翻成響應式網頁設計感覺很怪,我個人會叫他適用性網頁設計,畢竟,他是針對不同的裝置,所使用的不同的螢幕尺寸,而去回應不同的畫面結果,所以適用性或許比較容易理解(吧?
好,其實無關設計,我們來聊一下觸控事件。
在 w3c 的規範裡頭,觸控事件有下列幾種,
- touchstart
- touchmove
- touchend
- touchcancel
那,w3c 是怎麼解釋這些動作的呢?
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
,倘若你有綁定這個事件的話。
聽起來很玄妙,直接貼出實際例子可能比較好理解,
第一階段,是按一下螢幕,他便會取得 touchstart
與 touchend
兩個事件狀態。而第二階段,出現了 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
這項屬性質來取得,這是唯一的區別。那你說 touchstart
與 touchmove
會不會有 changedTouches
呢?是的,也會有。那我不就可以三種事件都取用 changedTouches
呢?當然不行。
為什麼不行?
因為數值會一樣!會一樣!會一樣!會一樣!會一樣!會一樣!
至於 touchcancel
一直以來都是一個謎團。根據 Safari Web Content Gride 的說明,有寫跟沒有寫一樣 所以我就不引述了。倒是有一個方法可以強制做出 touchcancel
的動作,
- 開啟 iPhone/Safari,進入你的網頁
- 按下
touchstart
事件的區塊 - 按下實體 Home 鍵
看起來蠻蠢的,但是我這樣作,的確會觸發 touchcancel
這個事件。
所以,大抵上來看,touchcancel
這個事件,是由裝置或是裝置內的應用程式,例如通知(Notification),所觸發的干擾,而瀏覽器在無法正確接受的情況下,會觸發 touchcancel
的事件。不過現在 iOS 已經把通知改為畫面上端顯示,並不會佔用螢幕,所以其實瀏覽器並不會受到干擾。
結語
沒事不要半夜測試 Touch,會被女友揍,早點上床睡覺比較好!