H5焦点图

焦点图算是最基础的控件了,很多地方都在用,在网上找了一圈,没发现原生JS的,都是各种框架和库,正好自己写一个,发现里面小门道还挺多的,想要体验好,各种细节都要去抠才行:

1、大家都知道,一般翻页都是滑动距离超过 50% 才翻页,但如果快速滑动,即使滑动距离很短,也是可以翻页的,

这里就需要记录滑动开始的时间和滑动结束的时间,还要记录滑动的距离,设定一个范围值就ok,我设的是滑动耗时在150毫秒以下,距离在60以上,切换焦点图。

2、手机滑动焦点图,方向肯定不是很标准的横或竖,左或右。

所以呢,这里就需要计算滑动的方向,也就是角度,根据角度来判断往前翻,还是往后翻,为了体验好,例如大于-45度并且小于45度的,都算往右滑动,查看详细说明文章

3、体验好的焦点图,都是可以无限循环翻页的。

实现的方式也比较取巧,首先要克隆第一张图,并插入到父节点的最后,当翻到最后一页,再往后翻的话,就会翻到克隆的这一页,然后如果再往后翻,当 touchstart 刚触发的时候,会把图重置到第一幅图(第一和最后的图都一样,所以不会察觉到),这样,便实现了循环。如果要从第一幅图,往前滑动(从 dom 节点上看,第一幅图前面是克隆的图),就把 transform 的值,直接设成真实的最后一幅图的值,就行了。

4、仔细体验一下,当你在滑动焦点图的时候,页面是无法上下滚动的。在页面上下滚动的时候,你又无法滑动焦点图。

这需要在滑动的时候设置一个开关变量,来控制 touchmove 的时候,是否执行代码,还要 touchmove 的时候阻止浏览器默认事件 event.stopPropagation(),来实现滑动焦点图的时候,页面无法上下滚动;

前面说过了,要通过计算滑动的角度,来分出4个方向,上下左右。只有左右两个方向,会执行 touchmove 的代码,页面会滚动,上下两个方向不执行代码,页面不滚动,也就实现了页面上下滚动,无法滑动焦点图。

附上代码:

//===================================================【焦点图组件】
//构造函数
function SLIDESHOW(container,params){
    this.dom = container; //焦点图元素
    this.pagination = params.pagination; //是否显示指示器
    this.loop = params.loop; //是否循环显示
    this.silderShow = this.dom.getElementsByClassName("slides")[0]; //焦点图容器
    this.btnWarp = this.dom.getElementsByClassName("btn")[0]; //指示器容器
    this.imgWidth = parseInt(this.dom.offsetWidth); //焦点图容器的宽度(移动的单位距离)
    this.imgNum = this.silderShow.getElementsByTagName("li").length; //焦点图的数量
    this.activeNum = 1; //指示器
    this.dataTransform = 0; //保存偏移值
    var $slides = this.silderShow; //焦点图容器
    var touchstart_x = 0; //滑动开始时的x坐标
    var touchstart_y = 0; //滑动开始时的y坐标
    var touchmoveX = 0; //滑动的x轴距离
    var touchmove_x = 0; //滑动时的x轴距离
    var touchmove_y = 0; //滑动时的y轴距离
    var touchend_x = 0; //滑动结束时的x坐标
    var moveSW = false; //是否滑动的开关
    var touchstart_time = 0; //滑动开始时的时间
    var touchend_time = 0; //滑动结束时的时间
    var touch_time = 0; //滑动耗时
    var sw = false; //如果滑动耗时在150毫秒以下,距离在60以上,切换焦点图
    var touch_fx = false; //根据起点和终点返回方向
    var t = this;

    //创建指示器
    if(!this.pagination){
    	this.btnWarp.style.display = "none";
    }
	var html = "";
    for(var i=0;i<this.imgNum;i++){
		html += "<li></li>";
    }
    this.btnWarp.innerHTML = "<ul>" + html + "</ul>";
    this.btnWarp.getElementsByTagName("li")[0].className = "active";

    //创建克隆DOM
    if(this.loop){
	    var sourceNode = this.silderShow.getElementsByTagName("li")[0]; // 获得被克隆的节点对象 
	    var clonedNode = sourceNode.cloneNode(true); // 克隆节点 
	    sourceNode.parentNode.appendChild(clonedNode); // 在父节点插入克隆的节点    
	}

    //滑动开始
    this.silderShow.addEventListener('touchstart', function(event) {

    	//重置
    	touch_time = 0;
    	touchmoveX = 0;

    	//阻止事件冒泡
		event.stopPropagation(); 

		//打开滑动的开关
		moveSW = true;

		//关闭过渡效果
		$slides.style.transitionDuration = "0s";

		//记录坐标
		touchstart_x = event.changedTouches[0].pageX;
		touchstart_y = event.changedTouches[0].pageY;

		//记录时间
		touchstart_time = new Date();

	}, false);

    //滑动中
	this.silderShow.addEventListener('touchmove', function(event) { 

		//阻止事件冒泡
		event.stopPropagation(); 

		if(moveSW){

			//记录坐标
			touchmove_x = event.changedTouches[0].pageX;
			touchmove_y = event.changedTouches[0].pageY;

			//每次滑动只获取一次
			if(!touch_fx){
				//根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动
				touch_fx = getSlideDirection(touchstart_x,touchstart_y,touchmove_x,touchmove_y);
			}
			
			//左右滑动焦点图
			if(touch_fx === 3 || touch_fx === 4){

				//阻止浏览器默认动作
				event.preventDefault(); 

				//滑动距离
				touchmoveX = parseInt(event.changedTouches[0].pageX) - parseInt(touchstart_x);
				var x = t.dataTransform + touchmoveX;
				$slides.style.transform = "translate3d(" + x + "px,0,0)";

				if(t.loop){
					//图片循环显示
					//向前翻循环
					if(x > 0 && x < 50){
						//重置到最后一张
						t.dataTransform = t.imgWidth * t.imgNum * -1;
					}

					//向后翻循环
					if(x < (t.imgWidth * t.imgNum * -1)){
						//重置到第一张
						t.dataTransform = 0;
					}
				}
			}

			//上下滚动页面
			else{
				moveSW = false;
			}
		}
	}, false);

	//滑动结束
    this.silderShow.addEventListener('touchend', function(event) { 

    	//阻止事件冒泡
		event.stopPropagation(); 

		//关闭滑动的开关
		moveSW = false;

		//删除获取的滑动方向
		touch_fx = false;

		//打开过度效果
		$slides.style.transitionDuration = "0.5s";

		//记录坐标
		touchend_x = event.changedTouches[0].pageX;
		
		//记录时间
		touchend_time = new Date();

		//记录滑动耗时
		touch_time = touchend_time.getTime() - touchstart_time.getTime();

		//如果滑动耗时在150毫秒以下,距离在60以上,切换焦点图
		if(touch_time <= 150 && Math.abs(touchmoveX) > 60){
			sw = true;
		}else{
			sw = false;
		}

		//发送移动的数值
		t.domMove(touchmoveX,sw);
		
	}, false);
}

//焦点图容器的移动
SLIDESHOW.prototype.domMove = function(num,sw){
	var moveNum;
	var index;
	var needWidth = this.imgWidth / 2;

	//滑动超过半个宽度,或有特殊指令,切换焦点图
	if(Math.abs(num) > needWidth || sw){

		//上一个
	    if(num > 0 && (this.loop ? true : this.activeNum > 1)){
	    //if(num > 0){
			moveNum = this.dataTransform + this.imgWidth * 1;
			this.moveAnimate(moveNum);
	    }

	    //下一个
	    else if(num < 0 && (this.loop ? true : this.activeNum < this.imgNum)){
	    //else if(num < 0){
			moveNum = this.dataTransform + this.imgWidth * -1;
			this.moveAnimate(moveNum);
	    }
	    
	    //不动
	    else{
			this.moveAnimate(this.dataTransform);
	    }

	}else{
    	//不动
    	this.moveAnimate(this.dataTransform);
    }
};

//焦点图的移动动画
SLIDESHOW.prototype.moveAnimate = function(moveNum){

	//移动动画
	this.silderShow.style.transform = "translate3d(" + moveNum + "px,0,0)";
	this.dataTransform = moveNum;

	//当前下标值
	this.activeNum = Math.abs(this.dataTransform) / this.imgWidth + 1;

	//指示器动画
	var $btnLi = this.btnWarp.getElementsByTagName("li");
	for(var i=0;i<$btnLi.length;i++){
		$btnLi[i].className = "";
	}

	var showIndex = this.activeNum > this.imgNum ? 0 : this.activeNum - 1;
	$btnLi[showIndex].className = "active";
};


//根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动
function getSlideDirection(startX,startY,endX,endY) {
  var dy = startY - endY;
  var dx = endX - startX;
  var result = 0;

  //如果滑动距离太短
  if (Math.abs(dx) < 2 && Math.abs(dy) < 2) {
  	 //未滑动
     return result;
  }

  //返回滑动的角度
  var angle = Math.atan2(dy,dx) * 180 / Math.PI;;

  if (angle >= -45 && angle < 45) {
  	 //向右
     result = 4;
  }

  else if (angle >= 45 && angle < 135) {
  	 //向上
     result = 1;
  }

  else if (angle >= -135 && angle < -45) {
  	 //向下
     result = 2;
  }

  else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) {
  	 //向左
     result = 3;
  }

  return result;
}

使用方法也很简单:

var mySlidShow = new SLIDESHOW($dom,{
    //是否显示指示器
    pagination: true,

    //是否循环显示
    loop: true,
});

猛击demo ☻

发表评论