2016/9/30 从移动端touch事件谈起

移动互联网终端的touch事件

touchstart 手指触摸屏幕的时候执行
touchmove 手指在屏幕上移动的时候执行
touchend 手指离开屏幕的时候执行
touchcancel 事件被强制终止的时候执行 (来电话了 短信 按home)

每个触摸事件都包括了三个触摸列表:
  1. touches :当前位于屏幕上的所有手指的一个列表。
  2. targetTouches :位于当前DOM元素上的手指的一个列表。
  3. changedTouches :涉及当前事件的手指的一个列表。
这些列表由包含了触摸信息的对象组成:
  1. identifier :一个数值,唯一标识触摸会话(touch session)中的当前手指。
  2. target :DOM元素,是动作所针对的目标。
  3. 客户/页面/屏幕坐标 :动作在屏幕上发生的位置。
  4. 半径坐标和 rotationAngle :画出大约相当于手指形状的椭圆形。

深入了解touch事件之前 有必要先了解 touch对象 ;

touch对象用来封装一次屏幕触摸,一般来自于手指。
它在touch事件触发的时候产生,可以通过touch事件处理器(event handler)机制的event对象取到;(一般是通过event.changedTouches属性)。
这个对象包括一些重要的属性:

client  / clientY:触摸点相对于浏览器窗口viewport的位置

pageX / pageY:触摸点相对于页面的位置

screenX /screenY:触摸点相对于屏幕的位置

identifier: touch对象的unique ID
移动平台 对meta标签的定义(很多种 ,先了解一种写法)
<meta name="viewport" content="width=device-width, initial-scale=1.0, 
maximum-scale=1.0, user-scalable=no" />
user-scalable – // 用户是否可以手动缩放 (no,yes)
maximum-scale – // 允许用户缩放到的最大比例
initial-scale – // 初始的缩放比例 (范围从 > 0 到 10)

demo1

一个div;手指触摸时显示 ;手指移动 div也跟着移动 ;手指离开 div消失

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"   content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
    <title>touch</title>

    <style>

        html,body,div{
            margin: 0;
            padding: 0;
        }

        #box{
            width: 50px;
            height: 50px;
            background-color: pink;
            display: none;
            position: absolute;
        }

        .show{
            //权重 !important
            display: block !important;
        }

    </style>

</head>
<body>

    <div id="box">

    </div>

    <script>

        var box = document.querySelector("#box");
        document.addEventListener("touchstart",function (e) {

            box.className = "show";

            var touch = e.changedTouches[0];

            box.style.left = touch.pageX - box.offsetWidth / 2 + "px";

            box.style.top = touch.pageY -  box.offsetHeight / 2 +"px";

        })

        document.addEventListener("touchmove",function (e) {

            var touch = e.changedTouches[0];

            box.style.left = touch.pageX - box.offsetWidth / 2 + "px";

            box.style.top = touch.pageY -  box.offsetHeight / 2 +"px";

        })
        document.addEventListener("touchend",function (e) {
            box.className = "";
        })
    </script>
</body>
</html>

demo2
做一个入下图所示无滚动条 可左右滑动的 且无论字数间距固定 不会换行的导航栏


手机端导航栏

代码如下

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
    <title>Document</title>
    <style type="text/css">
        html,body,div,ul,li{
            margin: 0;
            padding: 0;
        }
        #topbar{
            width: 100%;
            height: 44px;
            overflow: hidden;
        }
        #nav{

            height: 55px;
            overflow: auto;
            background-color: #0FA9D8;

        }
        #nav ul{
            white-space: nowrap;
            /*width: auto;*/
        }
        #nav ul li{
            display: inline-block;
            height: 44px;
            line-height: 44px;
            padding: 0 30px;
        }
    </style>

</head>
<body>
    <div id="topbar">
        <nav id="nav">
            <ul>
                <li>娱乐头条</li>
                <li>军事</li>
                <li>历史</li>
                <li>新闻</li>
                <li>美图</li>
                <li>旅游</li>
            </ul>
        </nav>
    </div>
</body>
<script>
    var ul = document.querySelector("ul");
    ul.addEventListener("touchstart",function(e){
        this.startTouch = e.changedTouches[0];
    })
    ul.addEventListener("touchmove",function(e){
        var touch = e.changedTouches[0];
        if(Math.abs(touch.pageX-this.startTouch.pageX)>10){
            this.isMove = true;
        }
    })
    ul.addEventListener("touchend",function(e){
        this.endTouch = e.changedTouches[0];
        if(Math.abs(this.endTouch.pageX-this.startTouch.pageX)<10 && !this.isMove){
            console.log("tap")
        }else{
            console.log("move");
        }
        if (e.target.nodeName=="LI") {
            console.log(e.target.innerHTML);
        }
    })
</script>
</html>

有一个问题 this.startTouch = e.changedTouches[0];这个获取到的是第一次触摸的事件 ,以后 全是以此为参照 ~有时间请教下别人

待理解的touch.js

/**
 * Created by lovering on 16/9/20.
 */




(function () {

    var typeObj = {
        tap:            true, //点击 和 抬起的位置 误差不超过容错范围 即算tap
        doubleTap:      true, //两次tap 时间在300ms 之内 算doubleTap
        swiperLeft:     true, //抬起的位置的X 相对于 开始的位置的 X 的差值 为正 并且 滑动距离大于某个值(40px) Y上线误差不能超过20px(误差为40px)
        swiperRight:    true, //抬起的位置的X 相对于 开始的位置的 X 的差值 为负 并且 滑动距离大于某个值(40px) Y上线误差不能超过20px(误差为40px)
        swiperDown:     true, //抬起的位置的Y 相对于 开始的位置的 Y 的差值 为正 并且 滑动距离大于某个值(40px) X上线误差不能超过20px(误差为40px)
        swiperUp:       true  //抬起的位置的Y 相对于 开始的位置的 Y 的差值 为负 并且 滑动距离大于某个值(40px) X上线误差不能超过20px(误差为40px)
    }

    var eventFn = {

        tap : function (e,callback) {

            if (Math.abs(this._endTouch.pageX - this._startTouch.pageX) <= 5 && Math.abs(this._endTouch.pageY - this._startTouch.pageY) <= 5 && !this._isMove){

                if (callback){

                    callback(e);
                }

            }

        },

        doubleTap : function (e,callback) {

            var t = this;
            
            isDouble(function () {

                //用自定义属性来存储点击的次数
                t._tapNum += 1;

                //判断是否开启过定时器 如果没开起过则进入判断 开启定时器 如果定时器开启说明当前为第二次点击
                if (!t._timer){

                    t._timer =  setTimeout(function () {

                        //300ms后判断当前的点击次数是否为2 如果为2则说明在300ms内点击了两次
                        //同时判断callback 是否存在 如存在说明外面给元素绑定过双击事件
                        //两个条件都满足的情况下 则回调双击事件的回调函数.
                        if (t._tapNum == 2 && callback){
                            
                            callback(e);

                        }else if (t._tapNum == 1){

                            //如果当前点击次数为1 说明没有进行双击 当前为单击 调用单击判断的方法 进行单击函数的回调
                            eventFn.tap.call(t,e,t._evevtType["tap"]);
                        }

                        //不管是否在300ms 内双击 我们都需要将 点击次数清零 定时器清除 并置空 以便下个周期 从新判断使用
                        t._tapNum = 0;

                        clearTimeout(t._timer);
                        
                        t._timer = null;
                        

                    },300)
                    
                }
                
            });

            function isDouble(callback) {

                if (Math.abs(t._endTouch.pageX - t._startTouch.pageX) <= 5 && Math.abs(t._endTouch.pageY - t._startTouch.pageY) <= 5 && !t._isMove){
                    
                    callback();
                }
            }

        },

        swiperLeft : function (e,callback) {
            
            if (this._endTouch.pageX - this._startTouch.pageX < -40 && Math.abs(this._endTouch.pageY - this._startTouch.pageY) <= 20){
                
                if (callback){
                    callback(e);
                }
                
                // callback && callback(e);
                //
                // callback ? callback(e) : null;
            }
            
            

        },

        swiperRight : function (e,callback) {

            if (this._endTouch.pageX - this._startTouch.pageX > 40 && Math.abs(this._endTouch.pageY - this._startTouch.pageY) <= 20){

                if (callback){
                    callback(e);
                }
            }

        },

        swiperDown : function (e,callback) {

            if (this._endTouch.pageY - this._startTouch.pageY > 40 && Math.abs(this._endTouch.pageX - this._startTouch.pageX) <= 20){

                if (callback){
                    callback(e);
                }
            }

        },

        swiperUp : function (e,callback) {

            if (this._endTouch.pageY - this._startTouch.pageY < -40 && Math.abs(this._endTouch.pageX - this._startTouch.pageX) <= 20){

                if (callback){
                    callback(e);
                }
            }
        }

    };



    HTMLElement.prototype.addEventListener = function (eventType,callback) {

        if (typeObj[eventType]){


            if (!this._isBind){

                this._isBind = true;

                this._evevtType = {};

                //第一次绑定的时候设置一个自定义属性用来存储点击次数 以便后面判断双击时使用
                this._tapNum = 0;

                //在第一次绑定的时候 给绑定元素设置一个对象的自定义属性 以绑定的事件类型为属性名 以 对应事件类型的callback 为属性值
                //把绑定过的事件进行存储.

                this._evevtType[eventType] = callback;

                EventTarget.prototype.addEventListener.call(this,"touchstart",function (e) {

                    this._startTouch = e.targetTouches[0];
                    
                    this._isMove = false;

                });

                EventTarget.prototype.addEventListener.call(this,"touchmove",function (e) {

                    var moveTouch = e.targetTouches[0];
                    
                    if (Math.abs(moveTouch.pageX - this._startTouch.pageX) > 5 && Math.abs(moveTouch.pageY - this._startTouch.pageY) > 5){

                        this._isMove = true;

                    }
                    

                });

                EventTarget.prototype.addEventListener.call(this,"touchend",function (e) {

                    this._endTouch = e.changedTouches[0];

                    for (var key in eventFn){

                        if (key != "tap"){

                            eventFn[key].call(this,e,this._evevtType[key]);

                        }

                    }

                });

            }else {

                //在第一次之后我们对每次绑定的事件进行存储
                this._evevtType[eventType] = callback;

            }



        }else {


            EventTarget.prototype.addEventListener.call(this,eventType,callback);
        }



    }


    HTMLElement.prototype.removeEventListener = function (eventType,callback) {


        if (typeObj[eventType]){
            
            if (this._evevtType[eventType] && this._evevtType[eventType] == callback){
                
                delete this._evevtType[eventType];
            }
            
        }else {
            
            EventTarget.prototype.removeEventListener.call(this,eventType,callback);
            
        }
        
    }
    
    
    
    
})();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容