移動端web開發---Touch事件詳解

一、pc端事件回顧

HTML事件、DOM0事件、DOM2事件

事件對象。

如果上述概念不清楚,請先去了解。

二、移動端事件簡介

2.1 pc端事件在移動端的問題

​ 移動設備主要特點是不配備鼠標,鍵盤也只是在需要輸入的地方纔會激活虛擬鍵盤。所以以前的pc端事件在移動端使用起來就沒有以前那麼爽了,雖然部分仍然可以使用。

  • click事件的300ms延遲問題。

    ​2007年第一代iphone發佈,由於那個年代所有的網頁都是針對大屏的pc端設計的,iphone的Safari瀏覽器爲了讓用戶瀏覽網頁的時候可以瀏覽到整個網頁,把viewport設置爲960px(參考前面的文章),好是好,但是由於縮放了整個頁面,導致內容變得非常小,視力6.0的都不一定看得清楚。

    ​所以Safari瀏覽器自帶了一個當時看起來相當酷的一個功能:雙擊縮放。你雙擊頁面的時候,瀏覽器會智能的縮放當前頁面到原始大小。

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

    ​所以,當你想執行click操作的時候,就感覺到了”卡頓”。如果點擊之後100ms之後沒有反應,基本就有卡頓的感覺。

  • dblclick事件失效

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

2.2移動端web新增touch事件

​隨着觸屏設備的普及,w3c爲移動端web新增了touch事件。

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

  • touchstart

    當在屏幕上按下手指時觸發

  • touchmove

    當在屏幕上移動手指時觸發

  • touchend

    當在屏幕上擡起手指時觸發

  • touchcancel

    當一些更高級別的事件發生的時候(如電話接入或者彈出信息)會取消當前的touch操作,即觸發touchcancel。一般會在touchcancel時暫停遊戲、存檔等操作。

三、相關知識詳解

​ 與移動端相關的interface主要有三個:

  1. TouchEvent

    表示觸摸狀態發生改變時觸發的event

  2. Touch

    表示用戶和觸屏設備之間接觸時單獨的交互點(a single point of contact)

  3. TouchList

    表示一組touches。當發生多點觸摸的時候才用的到。

3.1、TouchEvent詳解

​ 爲了區別觸摸相關的狀態改變,存在多種類型的觸摸事件。可以通過檢查觸摸事件的 TouchEvent.type 屬性來確定當前事件屬於哪種類型。

注意:在很多情況下,觸摸事件和鼠標事件會同時被觸發(目的是讓沒有對觸摸設備優化的代碼仍然可以在觸摸設備上正常工作)。如果你使用了觸摸事件,可以調用 event.preventDefault()來阻止鼠標事件被觸發。

var i = 1;
var box = document.querySelector("div");
var ps = document.querySelectorAll("p");
box.addEventListener("touchstart", function (e){
   ps[0].innerHTML = e.type + i++;
})
box.addEventListener("touchmove", function (e){
   ps[1].innerHTML = e.type + i++;
})
box.addEventListener("touchend", function (e){
   ps[2].innerHTML = e.type + i++;
})

3.1.1 touchstart

​ 當用戶手指觸摸到的觸摸屏的時候觸發。事件對象的 target 就是touch 發生位置的那個元素。

<div>
    <p></p>
</div>
<script>
    var i = 1;
    var box = document.querySelector("div");
    var p = document.querySelector("p");
    box.addEventListener("touchstart", function (e){
        p.innerHTML = e.target.tagName + ", " + i++;    //  P
    })

</script>

3.1.2 touchmove

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

touchmove 事件的target 與最先觸發的 touchstarttarget 保持一致。touchmove事件和鼠標的mousemove事件一樣都會多次重複調用,所以,事件處理時不能有太多耗時操作。不同的設備,移動同樣的距離 touchmove 事件的觸發頻率是不同的。

​ 有一點需要注意:即使手指移出了 原來的target 元素,則 touchmove 仍然會被一直觸發,而且 target 仍然是原來的 target 元素。

<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>

3.1.3 touchend

​ 當用戶的手指擡起的時候,會觸發 touchend 事件。如何用戶的手指從觸屏設備的邊緣移出了觸屏設備,也會觸發 touchend 事件。

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

3.1.4 touchcancel

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

  • 由於某個事件取消了觸摸:例如觸摸過程被一個模態的彈出框打斷。
  • 觸點離開了文檔窗口,而進入了瀏覽器的界面元素、插件或者其他外部內容區域。
  • 當用戶產生的觸點個數超過了設備支持的個數,從而導致 TouchList 中最早的 Touch對象被取消

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

3.2 TouchList詳解

​ 一個TouchList代表一個觸摸屏幕上所有觸點的列表。

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

TouchList有1個屬性,2個方法

  1. 只讀屬性:length

    返回這個TouchListTouch對的個數。(就是有幾個手指接觸到了屏幕)

  2. 方法:identifiedTouch()

    返回值是在TouchList中的第 1 個 Touch 對象,已經被標記爲過時,雖然有的瀏覽器還可以使用,但是不建議使用。

  3. 方法:item(index)

    返回TouchList中指定索引的Touch對象。

想獲取 TouchList對象,TouchEvent 的三個屬性可以獲取到 TouchList:

  1. changedTouches:(只讀)

    這個 TouchList對象列出了和這個觸摸事件對應的那些發生了變化的 Touch 對象

    • 對於 touchstart 事件, 這個 TouchList 對象列出在此次事件中新增加的觸點。
    • 對於 touchmove 事件,列出和上一次事件相比較,發生了變化的觸點。
    • 對於 touchend 列出離開觸摸平面的觸點(這些觸點對應已經不接觸觸摸平面的手指)
  2. targetTouches:(只讀)

    這個TouchList列出了那些 touchstart發生在這個元素,並且還沒有離開 touch surfacetouch point(手指)。是touches的一個嚴格子集。

  3. touches`:(只讀)

    這個 TouchList 列出了事件觸發時: touch suface上所有的 touch point

touches.length>=targetTouches.length
  1. 看下面的代碼:
<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>

  1. 看下面的代碼
<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詳解

​ 表示用戶和觸摸設備之間接觸時單獨的交互點(a single point of contact)。

​ 這個交互點通常是一個手指或者觸摸筆。

​ 觸摸設備通常是觸摸屏或者觸摸板。

js對象提供了多個屬性來描述Touch對象。

這些屬性分成兩大部分:

  1. 基本屬性(Basic properties)

  2. 觸摸區域相關屬性 (Touch area)

    Touch area 目前還在試驗階段。我們先不做過多的討論。

基本屬性:

這些屬性均是隻讀屬性

  1. identifier:

    表示每 1 個 Touch 對象 的獨一無二的 identifier。有了這個 identifier 可以確保你總能追蹤到這個 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){
           var touchList = e.changedTouches;
           for (var i = 0; i < touchList.length; i++){
               console.log(touchList.item(i).identifier);  
           }
       })
    
    </script>
  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

四、封裝移動端事件

前面的是最基本的事件,直接使用相對比較麻煩,所以有必要對一些常用事件進行封裝。

比如:單擊事件、雙擊事件、滑動方向等

(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 = "左側滑動了....."
})

參考資料:mdn相關文檔

發佈了79 篇原創文章 · 獲贊 375 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章