需求 移动端实现一个轮播器,在轮播器上层有一个静止图层,不会随着轮播器翻页而偏移。
[Github] ,欢迎Fork与Star!!!
需求分析 以上需求可以拆分成两个小的需求: 问题 轮播器作为一个图层,静止图层是一个。
那么那个图层在上,那个在下?
轮播器在上,则静止图层被轮播器遮盖; 静止图层在上,则绑定在轮播器上的手势事件由于被静止图层在上而无法触发。 再细分需求 为了显示静止图层,那么静止图层一定是要在上的。那么就必须解决第2个问题,怎么触发轮播器的手势。
实现 轮播器 由于网上的轮播器插件的都是封装好的,触发事件比较麻烦,因此选择自己使用原生js实现一个。
实现轮播器的方案有很多中,比如改变translate属性的,也有先实现一个scroll-view,然后改变scroll-view的scrollLeft。我选用后者(单纯是因为我没有这样做过,而且好像很有趣)。
一般scroll-view都是纵向的,根据以上需求,我们就需要实现一个横向的scroll-view,具体实现如下
1 2 3 4 5 6 7 <div  id ="app" >   <dl  class ="story" >      <dt > </dt > <dt > </dt >      <dt > </dt > <dt > </dt > <dt > </dt >      <dt > </dt > <dt > </dt >    </dl >  </div > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 *{ margin : 0 ;padding : 0 ; } html ,body { height : 100% ; }::-webkit-scrollbar{ display : none; } #app { height : 100% ;position : relative; }#app  > .story { position : absolute;top : 0 ;right : 0 ;bottom : 0 ;left : 0 ; }#app  > .story {   z-index : 1000 ; border : 1px  solid;   overflow-x : auto;overflow-y : hidden; white-space : nowrap;font-size : 0 ;letter-spacing : 0 ;      -webkit-transform : translateZ (0 );   -moz-transform : translateZ (0 );   -ms-transform : translateZ (0 );   -o-transform : translateZ (0 );   transform : translateZ (0 ); } .story  > dt { height : 100% ;width : 100% ;display : inline-block;vertical-align : top;position : relative; }.story  > dt :nth-of-type (1 ){ background-color : red; }.story  > dt :nth-of-type (2 ){ background-color : orange; }.story  > dt :nth-of-type (3 ){ background-color : yellow; }.story  > dt :nth-of-type (4 ){ background-color : green; }.story  > dt :nth-of-type (5 ){ background-color : cyan; }.story  > dt :nth-of-type (6 ){ background-color : blue; }.story  > dt :nth-of-type (7 ){ background-color : purple; }
核心css为
1 overflow-x : auto;overflow-y : hidden; white-space : nowrap;
禁用块级元素的换行,依次达到横排的效果。[查看源码:jsbin] [查看源码:jsfiddle] [查看源码:jsbin] ,并使用移动设备模式查看, 下同)
静止图层并监听其上的手势 先向#app中添加静止图层
1 2 3 4 5 6 <div  id ="app" >   <dl  class ="mash" >      <img  src ="http://ohi69gup6.bkt.clouddn.com/005TGG6vly1fes9jc0kk0g30b40b40tv.gif" >    </dl >    ... </div > 
然后,来分析一下轮播器的功能
向左,向右滑动翻页; 轮播器卡片手势跟随。 第1点的实现思路 获取手指 “触碰屏幕那刻” 和 “离开屏幕那刻” 的位置,根据以上两个位置判断翻页方向,要获取这两位置,就需要分别注册touchstart和touchend事件,通过事件的回调参数( event )获取。
1 2 3 4 var  touchStartX = e.changedTouches [0 ].pageX ;var  touchEndX = e.changedTouches [0 ].pageX ;
判断滑动方向
1 var  isNext = touchEndX - touchStartX < 0 ;
知道翻页方向就可以进行翻页
翻页如上文所说,通过改变卡片容器的scrollLeft。这里的容器是#app > .story。
1 document .querySelector ("#app > .story" ).scrollLeft 
想要实现翻页,还必须知道两个属性:
每页宽度:可以通过story.clientWidth获取; 当前页:使用全局变量记录当前是第几页。 具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var  touchStartX = 0 ;var  currentPage = 1 ;var  mash = document .querySelector ("#app > .mash" );var  story = document .querySelector ("#app > .story" );var  screenWidth = story.clientWidth ;mash && mash.addEventListener ('touchstart' , function (e ){   var  touch = e.changedTouches [0 ];  touchStartX = touch.pageX ; }, false ); mash && mash.addEventListener ('touchend' , function (e ){   var  touch = e.changedTouches [0 ];   var  touchEndX = touch.pageX ;   var  isNext = touchEndX - touchStartX < 0 ;   var  oldCurrentPage = currentPage;   var  maxPage = story.children .length ;   var  minPage = 1 ;   currentPage = isNext ? currentPage + 1  : currentPage - 1 ;   currentPage = currentPage > maxPage ? maxPage : currentPage;   currentPage = currentPage < minPage ? minPage : currentPage;   var  targetScrollLeft = (currentPage - 1 ) * screenWidth;      story.scrollLeft  = targetScrollLeft; }, false ); 
以上代码基本实现翻页,但是仅仅是通过每次 “增加/减少 一个screenWidth的距离” 达到翻页的目的,是不够的,因为你会发现翻页效果很生硬的,仅仅只有一帧。
下面让我们把翻页效果做得更加顺滑。
怎么做,使用定时器(setTimeout)?
不!不!不!当然不会!
我们使用requestAnimationFrame,至于为什么使用requestAnimationFrame而不使用setTimeout?请自行Google。接着,让我们先实现一个动画函数
1 2 3 4 5 6 7 8 function  animate (callback ) {  var  _animate = function (     var  isValid = callback && callback ();     isValid && requestAnimationFrame (_animate);   };   requestAnimationFrame (_animate); } 
ok,然后要做的就是将它运用到翻页的代码中!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var  step = 30 ;mash && mash.addEventListener ('touchend' , function (e ){      animate (function (     var  scrollLeft = story.scrollLeft ;     scrollLeft = isNext ? scrollLeft + step : scrollLeft - step;     if (isNext && scrollLeft > targetScrollLeft) {       story.scrollLeft  = targetScrollLeft;       return  false ;     } else  if (!isNext && scrollLeft < targetScrollLeft){       story.scrollLeft  = targetScrollLeft;       return  false ;     } else  {       story.scrollLeft  = scrollLeft;       return  true ;     }   }); }, false ); 
[查看源码:jsbin] [查看源码:jsfiddle] 
第2点的实现思路 第二点需要实现的功能是:轮播器卡片手势跟随。
要实现以上效果,就需要监听手指未离开屏幕的前的动作,注册touchmove事件,获取手指每次移动的位置!
1 2 3 4 5 6 7 8 mash && mash.addEventListener ('touchmove' , function (e ){   var  touch = e.changedTouches [0 ];   var  moveingfPageX = touch.pageX ;   var  distance = -(moveingfPageX - touchStartX);   var  currentPageScrollLeft = (currentPage - 1 ) * screenWidth;   story.scrollLeft  = currentPageScrollLeft + distance; }, false ); 
思路也是比较简单的,监听手指所在位置与初始位置touchStartX的距离,然后动态地改变story.scrollLeft。
另外,现在已经实现卡片的手势跟随,随之又可能有这么一种情况:
1 2 3 4 5 6 var  isNext = touchEndX - touchStartX < 0 ;currentPage = isNext ? currentPage + 1  : currentPage - 1 ; currentPage = currentPage > maxPage ? maxPage : currentPage; currentPage = currentPage < minPage ? minPage : currentPage; 
以上逻辑是:如果不是上一页就是下一页。并没有实现后悔功能。
为实现后悔功能,我做如下修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var  validFlipDistance = 30 ;mash && mash.addEventListener ('touchend' , function (e ){      var  isValid = Math .abs (touchEndX - touchStartX) >= validFlipDistance;      if (isValid) {     currentPage = isNext ? currentPage + 1  : currentPage - 1 ;     currentPage = currentPage > maxPage ? maxPage : currentPage;     currentPage = currentPage < minPage ? minPage : currentPage;   } else  {          isNext = !isNext;   } }); 
[查看源码: jsbin] [查看源码: jsfiddle]