1. 程式人生 > 其它 >10-移動端開發教程-移動端事件

10-移動端開發教程-移動端事件

在前端的移動Web開發中,有一部分事件只在移動端產生,如觸控相關的事件。接下來給大家簡單總結一下移動端的事件。

1. PC端事件在移動端的相容問題

1.1 click事件的200~300ms延遲問題

由於移動端預設的佈局視口寬度是980畫素,所以網頁文字非常小,為了快速讓網頁還原到原來的大小,Safari最新引入了雙擊縮放功能:使用者雙擊手機頁面的時候,瀏覽器會智慧的縮放當前頁面到原始大小。

​雙擊縮放的原理就是,當用戶click一次之後,瀏覽器會經過約300ms之後檢測是否再有一次click,如果有的話,就會縮放頁面。否則的話就是一個click事件。

由於雙擊縮放功能存在,click事件觸發就會有大約200~300ms的延遲。

1.2 dblclick事件失效

由於雙擊縮放的存在,pc端的dblclick事件也失效了。

2. 移動端特有的touch事件

由於移動端裝置大都具備觸控功能,所以移動端瀏覽器都引入了觸控(touch)事件。

touch相關的事件跟普通的其他dom事件一樣使用,可以直接用addEventListener來監聽和處理。

最基本的touch事件包括4個事件:

  1. touchstart: 當在螢幕上按下手指時觸發
  2. touchmove: 當在螢幕上移動手指時觸發
  3. touchend: 當在螢幕上抬起手指時觸發
  4. touchcancel 當一些更高級別的事件發生的時候(如電話接入或者彈出資訊)會取消當前的touch操作,即觸發touchcancel。一般會在touchcancel時暫停遊戲、存檔等操作。

2.1 touch事件與click事件同時觸發

在很多情況下,觸控事件和滑鼠事件會同時被觸發(目的是讓沒有對觸控裝置優化的程式碼仍然可以在觸控裝置上正常工作)。

因為雙擊縮放檢測的存在,在移動裝置螢幕上點選操作的事件執行順序:

touchstart(瞬間觸發) → touchend → click(200-300ms延遲)

如果你使用了觸控事件,可以呼叫 event.preventDefault()來阻止滑鼠事件被觸發。

2.2 touchstart事件

​ 當用戶手指觸控到的觸控式螢幕的時候觸發。事件物件的 target 就是touch 發生位置的那個元素。

<div>
    點選我!
</div>
<script>
    var box = document.querySelector("div");
    box.addEventListener("touchstart", function (e) {
       console.log('touchstart'); 
    });
</script>

2.3 touchmove事件

當用戶在觸控式螢幕上移動觸點(手指)的時候,觸發這個事件。一定是先要觸發touchstart事件,再有可能觸發 touchmove 事件。

​touchmove 事件的target 與最先觸發的 touchstart 的 target 保持一致。touchmove事件和滑鼠的mousemove事件一樣都會多次重複呼叫,所以,事件處理時不能有太多耗時操作。不同的裝置,移動同樣的距離 touchmove 事件的觸發頻率是不同的。

注意:

  1. 即使手指移出了 原來的target 元素,則 touchmove 仍然會被一直觸發,而且 target 仍然是原來的 target 元素。
  2. touchmove事件會多次重複觸發,由於移動端計算資源寶貴,儘量保證事件節流
<div>
    <p></p>
</div>
<script>
    var i = 1;
    var box = document.querySelector("div");
    var p = document.querySelector("p");
    box.addEventListener("touchmove", function (e){
        p.innerHTML = e.target.tagName + ", " + i++;
    })
</script>

2.4 touchend事件

​ 當用戶的手指抬起的時候,會觸發 touchend 事件。如何使用者的手指從觸屏裝置的邊緣移出了觸屏裝置,也會觸發 touchend 事件。

touchend 事件的 target 也是與 touchstart 的 target 一致,即使已經移出了元素。

一次完整的touch事件的觸發順序和過程

2.5 touchcancel事件

​ 當觸點由於某些原因被中斷時觸發。有幾種可能的原因如下(具體的原因根據不同的裝置和瀏覽器有所不同):

  • 由於某個事件取消了觸控:例如觸控過程被一個模態的彈出框打斷。
  • 觸點離開了文件視窗,而進入了瀏覽器的介面元素、外掛或者其他外部內容區域。
  • 當用戶產生的觸點個數超過了裝置支援的個數,從而導致 TouchList 中最早的 Touch物件被取消

touchcancel 事件一般用於儲存現場資料。比如:正在玩遊戲,如果發生了 。touchcancel 事件,則應該把遊戲當前狀態相關的一些資料儲存起來。

3. 觸控事件物件

TouchEvent 是一類描述手指在觸控平面(觸控式螢幕、觸控板等)的狀態變化的事件。這類事件用於描述一個或多個觸點,使開發者可以檢測觸點的移動,觸點的增加和減少,等等。

每 個 Touch 物件代表一個觸點; 每個觸點都由其位置,大小,形狀,壓力大小,和目標 element 描述。 TouchList 物件代表多個觸點的一個列表.

3.1 TouchEvent

TouchEvent的屬性繼承了 UIEvent Event

屬性列表:

  1. TouchEvent.changedTouches: 一個 TouchList 物件,包含了代表所有從上一次觸控事件到此次事件過程中,狀態發生了改變的觸點的 Touch 物件。
  2. TouchEvent.targetTouches: 一個 TouchList 物件,是包含了如下觸點的 Touch 物件:觸控起始於當前事件的目標 element 上,並且仍然沒有離開觸控平面的觸點。
  3. TouchEvent.touches: 一 個 TouchList 物件,包含了所有當前接觸觸控平面的觸點的 Touch 物件,無論它們的起始於哪個 element 上,也無論它們狀態是否發生了變化。
 <style>
    .box {
      width: 100px;
      height: 100px;
      border: 1px solid #09c;
      background-color: #0dc;
    }
  </style>
  <div class="box"></div>
  <script>
    window.onload = function() {
      var box = document.querySelector('.box');
      box.addEventListener('touchstart', function(e) {
        console.dir(e); // 檢視TouchEvent物件的屬性和方法
      });
    }
  </script>

3.2 TouchList詳解

​ 一個TouchList代表一個觸控式螢幕幕上所有觸點的列表。

​ 舉例來講, 如果一個使用者用三根手指接觸螢幕(或者觸控板), 與之相關的TouchList 對於每根手指都會生成一個 Touch物件, 共計 3 個.

  1. 只讀屬性:length 返回這個TouchListTouch對的個數。(就是有幾個手指接觸到了螢幕)
  2. 方法:item(index) 返回TouchList中指定索引的Touch物件。
<div>
    <p style="font-size: 50px; color: #ffffff;"></p>
</div>
<script>
    var box = document.querySelector("div");
    var p = document.querySelector("p");
    box.addEventListener("touchend", function (e){
        p.innerHTML = e.changedTouches.length;  //返回Touch物件的個數
        for(var i = 0; i < e.changedTouches.length; i++){
            //遍歷出來每個Touch物件
            console.log(e.changedTouches.item(i));
        }
    })
</script>

測試多個手機觸控式螢幕幕:

<div></div>
<p></p>
<script>
    var div = document.querySelector("div");
    var p = document.querySelector("p");
    div.addEventListener("touchstart", function (e){
        var msg = "touches.length: " + e.touches.length +
                "<br> targetTouches.length: " + e.targetTouches.length +
                "<br> changedTouches.length: " + e.changedTouches.length;
        p.innerHTML = msg;
    })
</script>

操作:

  1. 放1個手指在div上
  1. 先放1個手指在其他地方,然後再放1個手指在div
  1. 先放1個手指在其他地方,然後再逐漸放2個手指在div

3.3 Touch詳解

Touch表示使用者和觸控裝置之間接觸時單獨的互動點(a single point of contact)。​ 這個互動點通常是一個手指或者觸控筆,​ 觸控裝置通常是觸控式螢幕或者觸控板。

基本屬性列表(都是隻讀):

編號

屬性名

屬性說明

1.

identifier

表示每 1 個 Touch 物件 的獨一無二的 identifier。有了這個 identifier 可以確保你總能追蹤到這個 Touch物件。

2.

screenX

觸控點相對於螢幕左邊緣的 x 座標。

3.

screenY

觸控點相對於螢幕上邊緣的 y 座標。

4.

clientX

觸控點相對於瀏覽器的 viewport左邊緣的 x 座標。不會包括左邊的滾動距離。

5.

clientY

觸控點相對於瀏覽器的 viewport上邊緣的 y 座標。不會包括上邊的滾動距離。

6.

pageX

觸控點相對於 document的左邊緣的 x 座標。 與 clientX 不同的是,他包括左邊滾動的距離,如果有的話。

7.

pageY

觸控點相對於 document的左邊緣的 y 座標。 與 clientY 不同的是,他包括上邊滾動的距離,如果有的話。

8.

target

總是表示 手指最開始放在觸控裝置上的觸發點所在位置的 element。 即使已經移出了元素甚至移出了document, 他表示的element仍然不變

案例:

var box = document.querySelector("div");
var p = document.querySelector("p");
box.ontouchstart = function (e){
    var touchList = e.changedTouches;
    for (var i = 0; i < touchList.length; i++){
        var touch = touchList[i];
        var msg = `id : ${touch.identifier} <br>
                       screenX : ${touch.screenX} <br>
                       screenY : ${touch.screenY} <br>
                       clientX : ${touch.clientX} <br>
                       clientY : ${touch.clientY} <br>
                       pageX : ${touch.pageX} <br>
                       pageY : ${touch.pageY} <br>
                       target: ${touch.target.nodeName} <br>
                        `;
        p.innerHTML = msg;
    }
}

沒有左右滾動:

左右滾動:pageX 明顯大於 clientX

4. 封裝移動端tap事件

由於點選事件經常使用,如果用click會有延遲問題,一般我們會用touch事件模擬移動端的點選事件, 以下是封裝的幾個事件,僅供參考。

(function (window){  //傳入window,提高變數的查詢效率
    function myQuery(selector){  //這個函式就是對外提供的介面。
        //呼叫這個函式的原型物件上的_init方法,並返回
        return myQuery.prototype._init(selector);
    }
    myQuery.prototype = {
        /*初始化方法,獲取當前query物件的方法*/
        _init: function (selector){
            if (typeof selector == "string"){
                //把查詢到的元素存入到這個原型物件上。
                this.ele = window.document.querySelector(selector);
                //返回值其實就是原型物件。
                return this;
            }
        },
        /*單擊事件:
         * 為了規避click的300ms的延遲,自定義一個單擊事件
         * 觸發時間:
         *   當抬起手指的時候觸發
         *   需要判斷手指落下和手指抬起的事件間隔,如果小於500ms表示單擊時間。
         *   如果是大於等於500ms,算是長按時間
         * */
        tap: function (handler){
            this.ele.addEventListener("touchstart", touchFn);
            this.ele.addEventListener("touchend", touchFn);

            var startTime,
                endTime;

            function touchFn(e){
                e.preventDefault()
                switch (e.type){
                    case "touchstart":
                        startTime = new Date().getTime();
                        break;
                    case "touchend":
                        endTime = new Date().getTime();
                        if (endTime - startTime < 500){
                            handler.call(this, e);
                        }
                        break;
                }
            }
        },
        /**
         * 長按
         * @param handler
         */
        longTag: function (handler){
            this.ele.addEventListener("touchstart", touchFn);
            this.ele.addEventListener("touchmove", touchFn);
            this.ele.addEventListener("touchend", touchFn);
            var timerId;

            function touchFn(e){
                switch (e.type){
                    case "touchstart" :  //500ms之後執行
                        timerId = setTimeout(function (){
                            handler.call(this, e);
                        }, 500)
                        break;
                    case "touchmove" :
                        //如果中間有移動也清除定時器
                        clearTimeout(timerId)
                        break;
                    case "touchend" :
                        //如果在500ms之內抬起了手指,則需要定時器
                        clearTimeout(timerId);
                        break;
                }
            }
        },
        /**
         * 左側滑動。
         * 記錄手指按下的左邊,在離開的時候計算 deltaX是否滿足左滑的條件         
         */
        slideLeft: function (handler){
            this.ele.addEventListener("touchstart", touchFn);
            this.ele.addEventListener("touchend", touchFn);
            var startX, startY, endX, endY;

            function touchFn(e){
                e.preventDefault();
                var firstTouch = e.changedTouches[0];
                switch (e.type){
                    case "touchstart":
                        startX = firstTouch.pageX;
                        startY = firstTouch.pageY;
                        break;
                    case "touchend":
                        endX = firstTouch.pageX;
                        endY = firstTouch.pageY;
                        //x方向移動大於y方向的移動,並且x方向的移動大於25個畫素,表示在向左側滑動
                        if (Math.abs(endX - startX) >= Math.abs(endY - startY) && startX - endX >= 25){
                            handler.call(this, e);
                        }
                        break;
                }
            }
        },
        /* 右側滑動 */
        rightLeft: function (e){
            //TODO:
        }
    }
    window.$ = window.myQuery = myQuery;
})(window);

// ========================
// 使用:
$("div").tap(function (e){
    console.log("單擊事件")
})
$("div").longTag(function (){
    console.log("長按事件");
})
$("div").slideLeft(function (e){
    console.log(this);
    this.innerHTML = "左側滑動了....."
})

5. 觸控手勢封裝相關的框架及事件

手勢相關的事件一般就是tap類(觸屏)和滑動(swipe)事件兩類。都是基於原生的touchstart、touchmove、touchend事件,封裝成不同的手勢型別自定義事件。

5.1 tap類事件

觸碰事件,我目前還不知道它和touch的區別,一般用於代替click事件,有tap longTap singleTap doubleTap四種之分。

  1. tap: 手指碰一下螢幕會觸發
  2. longTap: 手指長按螢幕會觸發
  3. singleTap: 手指碰一下螢幕會觸發
  4. doubleTap: 手指雙擊螢幕會觸發

5.2 swipe類事件

滑動事件,有swipe swipeLeft swipeRight swipeUp swipeDown 五種之分。

  1. swipe:手指在螢幕上滑動時會觸發
  2. swipeLeft:手指在螢幕上向左滑動時會觸發
  3. swipeRight:手指在螢幕上向右滑動時會觸發
  4. swipeUp:手指在螢幕上向上滑動時會觸發
  5. swipeDown:手指在螢幕上向下滑動時會觸發

5.3 zepto的手勢相關事件

Zepto.js 是一個輕量級的針對現代高階瀏覽器的JavaScript庫, 它適配了jQuery的大部分api,也就是jQuery怎麼用,Zepto.js就怎麼用。它非常小,非常適合移動端。

Zepto.js的touch模組中封裝了手勢相關的程式碼。封裝了再觸控裝置上觸發tap– 和 swipe– 相關事件,也適用於所有的touch(iOS, Android)和pointer事件(Windows Phone)。

  • 觸屏事件:tap、singleTap、doubleTap、longTap(>750ms)
  • 滑動事件:swipe、swipeLeft,、swipeRight,、swipeUp,、swipeDown
<style>.delete { display: none; }</style>

<ul id=items>
  <li>List item 1 <span class=delete>DELETE</span></li>
  <li>List item 2 <span class=delete>DELETE</span></li>
</ul>

<script>
$('#items li').swipe(function(){
  $('.delete').hide()
  $('.delete', this).show()
})

$('.delete').tap(function(){
  $(this).parent('li').remove()
})
</script>

5.4 其他移動端手勢相關庫

  1. 百度雲的touch.js
  2. hammer.js hammer提供了不僅僅tap、swipe等事件,還提供了:pan(平移)、pinch類(捏拿縮放)、 press類(按住)、 rotate類(旋轉)類手勢支援, hammer.js詳解教程

6. 移動端點選穿透問題

如果某個返回按鈕的位置,恰好在要返回的這個頁面的帶有href屬性的a標籤的範圍內,在點選返回按鈕後,頁面快速切換到有a標籤的頁面,300ms後觸發了click事件,從而觸發了a標籤的意外跳轉,這個就是典型的點選穿透問題。罪魁禍首其實就是a標籤跳轉預設是click事件觸發,而移動端的touch事件觸發之後,依然會在300ms後觸發click事件。

解決辦法: 1.就是阻止觸發touch事件完成後的click事件。 2.不要混用touch和click事件。顯然不可能都繫結click事件,因為要解決300ms延遲問題(除了fastclick),那麼只能都繫結touch事件,這樣click事件永遠不會被觸發。

注意:zepto並沒有阻止click事件,所以使用zepto的tap事件依然會導致點選穿透問題,你需要手動新增 e.preventDefault() 來阻止click事件。


參考文章:

  1. 移動端web開發---Touch事件詳解
  2. MDN:TouchEvent
  3. 移動端前端常見的觸控相關事件touch、tap、swipe等整理