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

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

idt ref nbsp 總結 間接 mage 邊緣 play 使用

在前端的移動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的屬性繼承了 UIEventEvent

屬性列表:

  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上


    技術分享圖片
  2. 先放1個手指在其他地方,然後再放1個手指在div
    技術分享圖片
  3. 先放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等整理

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