移动端的touch事件-滑动小插件(五)

前面说了很多关于touch事件的东西,对我来说,对touch事件的了解,暂时也就到这里了,也许有很多的问题,暂时还没有了解到,这个只能在以后的项目中,当碰到这方面的问题时,再做扩展了。

一:概述

本篇就结合之前碰到过的一些问题,整理出一个包含几个简单功能的小插件。Ok,话不多说,下面就直接开始吧。

二:Slide构造函数

该插件是使用的构造函数的方法写的。构造函数的名称就是:Slide。并且,该构造函数时一个基于jQuery的,所以,该方法,最适合的是使用在jQuery的项目中了,当然,我这里只是因为有点懒,所以不想着再自己去处理那些兼容方面的问题了,只是为了能就touch事件做个说明,当然,这里主要想说明的不是兼容问题,而是touch的使用而已,所以~~

1:看下Slide构造函数
关于这些,我觉得还是直接上代码为好,说明请参考代码中的注释:


function Slide(Soption,option){
    /**
     *Soption支持三个有效参数
     *selecter支持滑动的区域,touch事件会绑定到该元素
     *selected 使用事件委托时,支持的滑动区域,
		此时,selecter为selected的父元素
     *callback 回调函数,当触发时,进行回调的,
	    该回调,可以使单个的function,
	    也可以使一个function组成的Arrary数组。
    **/
	
    this.options = {
	//一些控制信息,
	//range表示,当移动量超过这个像素大小时,被判断为滑动操作,否则为点击操作
	range:50,
		
	//是否阻止a事件的跳转,默认不阻止(false),true时会被阻止
	preventAclick:false,
		
	//是否在页面跳转前,弹出一个信息提示
	showAMessage:true
    };
	
    //记录touch事件的开始的坐标
    this.start = {
	x:0,
	y:0
    };
	
    //记录touch事件的结束的坐标
    this.end = {
	x:0,
	y:0
    }
	
    //合法性验证,以及初始值设置
    this.selecter = (typeof Soption.selecter == "string")?Soption.selecter : document;
	
    //合法性验证,以及初始值设置
    this.selected = (typeof Soption.selected == "string")?Soption.selected : "";
	
    //合法性验证,以及初始值设置
    this.callback = (Soption.callback instanceof Function || Soption.callback instanceof Array )?Soption.callback:"";
	
    //初始化一些option的控制信息,
    //也就是更改options对象中的一些属性值。
    this.initSlide(option);
}


在构造函数中,会保存一些属性,至于保存什么样的属性,这些属性的选择方面,是有一些药注意的地方的,如果对于每一个实例中,该属性的值或者方法,是不同的,那么就直接把这个属性保存到构造函数内部;

而如果对于每一个实例来说,这个属性和方法是完全相同的,那么就把它们存放在原型链中,那么这些属性和方法,对于每一个实例就可以共用,而不需要在每次实例化时,重新定义,这也是构造函数的模式下的一个优势。

所以,从上述的构造函数中,定义的属性都是私有的属性,对于每一个实例,它们都可能是不同的,而其中,只有在最后,调用了一下初始化Slide的方法,也就是this.initSlide(option);方法,该方法是存在于原型链中的,它的作用是初始化该Slide方法,使其可用。

接下来,看下initSlide函数内部,具体初始化了哪些信息呢。


Slide.prototype.initSlide = function(option){
    //初始化一些参数
    this.setOptions(option);
	
    //绑定事件
    this.onEvent();
}


目前,该插件中,只对控制参数和事件绑定进行了初始化。

那么this.setOptions(option);中,是如何进行了参数的更新呢,这个就类似于extend方法了,就是把option中的属性和方法,扩展到实例内部的options对象中去了,覆盖默认的值。


Slide.prototype.setOptions = function(option){
    var i,options = this.options;
    
    option = typeof option == "object"?option:{};

    for(i in option){
	options[i] = option[i];
    }
}


而初始化事件的绑定时(this.onEvent();),进行了以下的处理:


Slide.prototype.onEvent = function(){
    var selecter = this.selecter,
	selected = this.selected;
	
    //根据是否有selected来进行不同方式的绑定
    if(selected != ""){
	//如果不存在,则直接绑定
	$(selecter).on("touchstart",selected,this.touchStart());
	$(selecter).on("touchend",selected,this.touchEnd());
    }else{
	//如果存在,则使用事件委托的方式进行绑定
	//如果有滑动加载的功能,同时需要新添加的元素,也支持滑动,
	//那么必须使用事件委托的方法进行初始化Slide了。
	$(selecter).on("touchstart",this.touchStart());
	$(selecter).on("touchend",this.touchEnd());
    }
    //阻止a链接的默认动作,对于a链接的跳转,使用JS在代码中处理。
    //原因是,IOS系统中,click事件会延迟0.5秒,并且当存在touch事件时
    //其中的a链接,基本不会被触发
    //而在Android下,a链接是可以触发的
    //为了能统一触发a链接跳转,则阻止a的默认动作,统一使用JS进行跳转
    $(selecter).on("click","a",this.prevDefault);
}


Slide.prototype.offEvent = function(){
    //解除事件的绑定,与onEvent相同。
    var selecter = this.selecter,
	selected = this.selected;
	
    if(selected != ""){
    	$(selecter).off("touchstart",selected);
	$(selecter).off("touchend",selected);
    }else{
	$(selecter).off("touchstart");
	$(selecter).off("touchend");
    }
    $(selecter).off("click","a",this.prevDefault);
}


同时,在绑定事件中,又引入了几个新的方法,分别是touchstart的回调函数this.touchStarttouchend事件的回调函数this.touchEnd,和阻止a标签默认动作的回调函数this.prevDefault,下面继续看下这几个回调函数的处理。

先看最简单的阻止默认动作的回调函数,感觉没有什么好说的~~~


Slide.prototype.prevDefault = function(e){
    //阻止默认事件的一个回调
    e.preventDefault();
    return false;
}


继续,touchstart的回调函数this.touchStart


Slide.prototype.touchStart = function(){
    var that = this;
	
    return function(e){
	e.preventDefault();
	var startIm = e.changedTouches && e.changedTouches[0] || e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] || {},
	    start = that.start;
		
	start.x = startIm.pageX || 0;
	start.y = startIm.pageY || 0;
    }
}


这里采用了一个闭包的写法,这也是很无奈的一种写法吧,因为这里我们需要的this指向是不同的:

因为该方法是定义在原型链中,而原型链中的方法,如果需要访问其他的属性时,是需要使用this关键字去查找对应的方法的,所以这个时候,我们需要this是指向实例的。

而这个方法,又是touchstart事件的回调函数,我们都知道,在事件的回调函数中,this是指向绑定该事件的DOM元素的,所以这个时候的this是指向HTMLDOM元素的。

所以,这里就使用了闭包的处理,当绑定事件时(请参考onEvent内部的事件绑定代码),直接运行了touchStart函数,并且在运行时,保存一个私有变量that=this,这个时候,this是指向实例的,然后返回一个新的function函数,作为touchstart被触发时的回调函数。也是就是touchStart函数内部return的一个匿名函数。

而这个匿名函数作为回调函数时,内部的this就是指向事件触发的DOM元素的,并且,根据作用域链的查找方法,这个时候,就可以访问到之前定义的that对象,用于调用实例中的一些属性和方法了。关于作用域链的问题,请查看:浅析作用域链–JS基础核心之一

上述的说明,完全是按照自己的想法进行说明的,如有不恰当的地方,请指教!

同理,touchend时,也是做了相同的处理,前面的touchStart代码中,并没有添加注释,如有疑问,请对比下述touchEnd代码中的注释:


Slide.prototype.touchEnd = function(){
    //使用闭包保存this关键字到that中,以便于下面的匿名函数,
    //在回调时,通过作用域链访问到实例对象中的其他的方法和属性
    var that = this;
	
    return function(e){
	e.preventDefault();
	var endIm = e.changedTouches && e.changedTouches[0] || e.originalEvent && e.originalEvent.changedTouches[0] || {},
	    //endIm的取值,是e.changedTouches是原生的JS中,touch相关参数的保存位置
	    //而在jQuery中,进行了少许的修改,把原生的一些信息
	    //都保存到了originalEvent属性中,所以,这个时候
	    //使用e.originalEvent.changedTouches进行的取值。
		
	    end = that.end,
	    //end,是取值在Slide实例内部定义的end对象,
	    //用于暂存touchend事件时,触点的位置
	    dir = "";
		
	end.x = endIm.pageX || 0;
	end.y = endIm.pageY || 0;
		
	//使用that访问实例中的getDir方法,
	dir = that.getDir();

	//使用that访问实例中的nextAction方法,并且传入参数
	//参数中$(this),表示,触发该事件的目标DOM元素对象。
	//以便在之后的代码和回调中,对该元素进行处理。
	that.nextAction(dir,$(this));
    }
}


OK,上述的问题,也算是就这么解决了,其实还有一个方法解决这个问题,就是把这两种方法,定义到实例内部去,就是在构造函数内部直接定义,并且在构造函数内部,使用that保存一个变量保存this即可。从而使得当被回调时,依照作用域链,可以访问到实例中的this,这个有兴趣的可以自己试试~~这里省略了~~

好了,这样再看上述代码中新引入的两个方法:getDirnextAction方法。

首先是getDir方法,该方法,是来确定,滑动的方向的问题:


Slide.prototype.getDir = function(e){
    var start = this.start,
	//先获取到之前在touchStart和touchEnd中
	//获取到的触点的位置
	end = this.end,
	//根据触点的位置,计算前后两次触点的间距
	x = start.x - end.x,
	y = start.y - end.y,
	range = this.options.range,
	//根据间距的大小,和间距的正负,判断手指滑动的方向
	dirX = (Math.abs(x) < range)?"":x>0?"left":"right",
	dirY = (Math.abs(y) < range)?"":y>0?"top":"bottom";

    //把之前触点的信息,进行归零,
    //防止一些意外出现
    start.x = 0;
    start.y = 0;
    end.x = 0;
    end.y = 0;
	
    //把计算的方向,返回,优先考虑返回y方向的移动
    return dirY || dirX;
}


Ok,继续之前的nextAction的方法实现:


Slide.prototype.nextAction = function(dir,obj){
    /**
	*dir是一个字符串,为移动的方向
	*obj是触发touch事件的DOM元素
    **/
    var preventA = this.options.preventAclick || false;
	//首先,先判断,是否阻止了a链接的跳转,默认是没有阻止的
	//即如果阻止了a链接的跳转,则不使用JS进行a链接跳转
	
    if(dir == ""){
	obj = obj.find("a");
	//如果没有阻止a链接的跳转,并且元素内部有a链接
	//那么则执行Aclick方法,使用JS进行a链接的跳转
	!preventA && (obj.size() > 0) && this.Aclick(obj);
    }else{
	//否则的话,则判断滑动的方向是否正确,并根据滑动,调用回调函数
	this.isSlided(dir) && this.callBackAction(obj,dir);
    }
}


继续,该方法中,又引入了三个新的方法,就是使用JS去实现a链接跳转的Aclick方法,和判断获取的移动方向是否合法的isSlided方法,和执行回调函数的callBackAction方法。

首先,最简单的isSlided方法,使用正则额表达式进行的处理,因为正则是使用的同一个不变的正则,所以这里直接把正则写在了原型链中,源代码如下:


//Slide的方向的正则,只支持这四种方向
Slide.prototype.isSlideReg = /^(left|right|top|bottom)$/;

Slide.prototype.isSlided = function(dir){
    return (this.isSlideReg).test(dir);
}


该部分,没有什么好说的,直接使用的正则。

继续Aclick方法,实现使用JS跳转a链接的页面:


Slide.prototype.Aclick = function(obj){
    //obj是所有的a链接的jQuery对象的集合。
    //按照第一个a标签的属性操作。
    obj = obj.eq(0);
    var message = "",alertMessage = this.options.showAMessage || false,
	href = obj.attr("href");
	
    if(alertMessage){
	//判断是否需要填出一些提示信息,默认是不需要弹出的
	//如果需要的话,则把弹出的提示信息保存在a标签的data-msg属性中即可。
	message = (obj.attr("data-msg") || "");
	message && alert(message);
    }
	
    //延时跳转页面,否则IOS下,会被alert影响。
    if(href){
	//如果有跳转地址,那么进行跳转
	setTimeout(function(){
	    location.href = href;
	},100);
    }

}


Ok,继续callBackAction方法


Slide.prototype.callBackAction = function(obj,dir){
    var callback = this.callback,
	i,len;
	
    if(callback instanceof Function){
	//根据callback的值的不同,使用不同的方法,并且改变回调 函数内部this的指向为obj对象。
	callback.call(obj,obj,dir);
    }else if(callback instanceof Array){
	//如果callback为数组,则,顺序调用
	i=0;
	len=callback.length;
	for(;i<len;i++){
	    callback[i].call(obj,obj,dir);
	}
    }
}


这里就算是完了,也有些方法是没有写入的,比如,这里如果我们想要添加回调函数,其实可以在原型链中添加一个addCallBack的方法,然后把新添加的回调函数,添加到callback数组中去,也可以添加一个delCallBack方法,来减掉某一个回调等等,这里就不再继续,下面就看下一些例子;

使用实例

直接看代码吧,添加多个完全相同的div模块,如下:


<div class = "main slide1">
    <a href = "http://www.zhangyunling.com" data-msg = "to zhangyunling">slide1,默认的方法,但只有一个回调函数</a>
</div>
<div class = "main slide2">
    <a href = "http://www.zhangyunling.com" data-msg = "to zhangyunling">slide2,不进行跳转,也不弹信息</a>
</div>
<div class = "main slide3">
    <a href = "http://www.zhangyunling.com" data-msg = "to zhangyunling">slide3,不弹出提示信息,但会执行跳转</a>
</div>
<div class = "main slide4">
    <a href = "http://www.zhangyunling.com" data-msg = "to zhangyunling">slide4,range为0,所以都会变为滑动,每次执行回调函数</a>
</div>


CSS部分,这里就不说了,main也只是添加了个高度,内边距和背景色而已

JS的处理,先引入jquery.jsslide.js,然后在本页进行下面的处理:


function callback1(obj,dir){
    //第一个回调函数
    alert("callback1="+dir);
};
function callback2(obj,dir){
    //第二个回调函数
    alert("callback2="+dir);
}

new Slide(
    {selecter:".slide1",callback:callback1}
);
new Slide(
    {selecter:".slide2",callback:[callback1,callback2]},
    {preventAclick:true}
);
new Slide(
    {selecter:".slide3",callback:[callback1,callback2]},
    {showAMessage:false}
);
new Slide(
    {selecter:".slide4",callback:[callback1,callback2]},
    {range:0}
);


OK,示例就是这样了,下面是demo地址:

静态页网址二维码:

源代码查看网址:http://www.zhangyunling.com/study/2014/20140921/slide_demo.html

打包下载demo地址:Slide demo打包下载

总结

一个简单的touch事件的使用,就这样吧,更多情况,还需要以后注意。

本文地址:http://www.zhangyunling.com/?p=248

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>