需求 移动端实现一个轮播器,在轮播器上层有一个静止图层,不会随着轮播器翻页而偏移。 以下是两个已经实践的两个项目:
[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] (ps:要看效果的gay man请点击[查看源码: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]