移动轮播图我看到两类,
一款是无线天猫的m.tmall.com和携程,实现了无缝轮播。
一款是蘑菇街的,没有实现无缝轮播。
我自己重写一个,类似天猫。
1.页面代码
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml" lang="UTF-8"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>基于jQuery的移动轮播图(支持触屏)</title> 6 <meta name="viewport" content="maximum-scale=1,initial-scale=1,user-scalable=0"> 7 8 <style type="text/css"> 9 10 img 11 { 12 border:0; 13 *display:inline; 14 } 15 li 16 { 17 border:0; 18 list-style-type :none; 19 } 20 ul 21 { 22 list-style:none; 23 margin:0; 24 padding:0; 25 } 26 body{ 27 overflow-x:hidden; 28 margin:0 auto; 29 padding:0; 30 width: 100%; 31 height: 100%; 32 } 33 .WSCSlideWrapper{ 34 width:100%; 35 position: relative; 36 margin:0 auto; 37 //cursor:move; 38 } 39 #WSCSlideWrapperTwo{ 40 width:50%; 41 position: relative; 42 margin:10px auto; 43 //cursor:move; 44 } 45 46 </style> 47 <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/jquery-1.8.2.min.js"></script> 48 49 <script type="text/javascript" src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/touchslide-1.1.js"></script> 50 </head> 51 <body> 52 53 54 <div class="WSCSlideWrapper" id="WSCSlideWrapper" > 55 56 <div> 57 58 <a href="http://www.baidu.com"><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1.jpg" /></a> 59 <a href="http://m.tmall.com"><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/2.jpg" /></a> 60 <a href="http://huaban.com"><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/3.jpg" /></a> 61 </div> 62 63 </div> 64 65 <div class="WSCSlideWrapper" id="WSCSlideWrapperTwo" > 66 <div> 67 <a><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1 (1).jpg" /></a> 68 <a><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1 (2).jpg" /></a> 69 <a><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1 (3).jpg" /></a> 70 <a><img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1 (4).jpg" /></a> 71 </div> 72 73 </div> 74 <script type="text/javascript"> 75 76 $(document).ready(function(){ 77 78 $('.WSCSlideWrapper').height($('.WSCSlideWrapper').width()*0.3); 79 $('#WSCSlideWrapperTwo').height($('#WSCSlideWrapperTwo').width()*1.5); 80 $('.WSCSlideWrapper').touchslide({timecontrol:3000,animatetime:300,direction:'left',navshow:true,canvassuport:true}); 81 }); 82 83 84 </script> 85 </body> 86 </html>
2.js插件代码
1 /* 2 * touchslide 1.1 3 * Copyright (c) 2014 BowenLuo http://www.luobo.com/ 4 * Date: 2014-06-08 5 * Example:$('.WSCSlideWrapper').touchslide({timecontrol:3000,animatetime:300,direction:'left',navshow:true,canvassuport:true}); 6 * Update:增加对IE9+等非Safari内核浏览器的鼠标拖动图片功能 7 */ 8 (function($){ 9 $.fn.touchslide = function(options){ 10 var defaults = { 11 timecontrol:3000,//图片停留时间 12 animatetime:300, //图片滑动所需时间 13 direction:'left', //轮播方向 14 navshow:true,//是否显示图片导航栏 15 canvassuport:true//图片导航栏是否开启cavas绘制圆 16 } 17 var options = $.extend(defaults, options); 18 var timecontrol = options.timecontrol||3000; 19 var animatetime = options.animatetime||300; 20 var direction = options.direction||'left'; 21 var navshow = options.navshow; 22 this.each(function(){ 23 var slideWrapper=$(this); 24 var slideImgWrapper = slideWrapper.children('div:first'); 25 var slideAs = slideImgWrapper.children('a'); 26 var slideImgs = slideAs.children('img'); 27 var imgcount = slideImgs.length; 28 var imgcountrealy = slideImgs.length; 29 var navimgs; 30 var circlewrapper; 31 var circles; 32 var canvassuport = true; 33 var circler = 0; 34 if(imgcount>1){ 35 $((slideImgWrapper.html().split("/a>")[0]+"/a>"+slideImgWrapper.html().split("/a>")[1]+"/a>")).insertAfter(slideAs.last()); 36 if(navshow){ 37 $("<div class='navimgs'></div>").insertAfter(slideImgWrapper); 38 navimgs = slideWrapper.children('div:last'); 39 navimgs.append("<div class='circlewrapper'></div>"); 40 circlewrapper = navimgs.children('div:first'); 41 for(var i=0;i<imgcountrealy;i++){ 42 circlewrapper.append("<canvas class='circle' width='15' height='15'></canvas>"); 43 } 44 circles = circlewrapper.children('canvas'); 45 var myCanvas = circles[1]; 46 if (!myCanvas.getContext) 47 { 48 canvassuport =false; 49 }else 50 { 51 canvassuport =options.canvassuport; 52 } 53 } 54 } 55 slideAs = slideImgWrapper.children('a'); 56 slideImgs = slideAs.children('img'); 57 imgcount = slideImgs.length; 58 var slideWrapperWidth = slideWrapper.width(); 59 var slideWrapperHeight = slideWrapper.height(); 60 slideWrapper.css({"overflow":"hidden","width":slideWrapperWidth,"height":slideWrapperHeight}); 61 slideImgWrapper.css({'position':"absolute","z-index":"1","overflow":"hidden","width":slideWrapperWidth*imgcount,"height":slideWrapperHeight}); 62 slideAs.css({'position':"relative","overflow":"hidden","float":"left","width":slideWrapperWidth,"height":slideWrapperHeight}); 63 slideImgs.css({'position':"relative","z-index":"1","width":slideWrapperWidth,"height":slideWrapperHeight}); 64 if(typeof(navimgs)!=='undefined'){ 65 if(navshow){ 66 navimgs.css({"position":"absolute","z-index":"3","overflow":"hidden","display":"block","width":slideWrapperWidth,"height":slideWrapperWidth*0.05,"bottom":"0"}); 67 if(slideWrapperWidth*0.05>15){ 68 navimgs.height(24); 69 } 70 circlewrapper.css({"position":"relative","overflow":"hidden","width":(slideWrapperWidth*0.05*imgcountrealy),"height":slideWrapperWidth*0.04,"margin":"auto"}); 71 circles.css({'position':"relative","float":"left","max-width":15,"max-height":15,"margin-left":slideWrapperWidth*0.01}); 72 if(slideWrapperWidth*0.03>15){ 73 circler=15; 74 }else{ 75 circler = slideWrapperWidth*0.03; 76 } 77 circles.attr("width",circler); 78 circles.attr("height",circler); 79 canvacircle(0); 80 for(var i=0;i<circles.length;i++){ 81 navclick(i); 82 } 83 }else{ 84 navimgs.css({"display":"none"}); 85 } 86 } 87 if(imgcount<4){ 88 return; 89 } 90 slideImgWrapper.css({'left':-slideWrapperWidth}); 91 var st; 92 var sts; 93 sts = setTimeout(function(){ 94 timedCount(); 95 },timecontrol); 96 97 slideWrapper.hover(function(){ 98 stopAll(); 99 }, 100 function(){ 101 sts = setTimeout(function(){ 102 timedCount(); 103 },timecontrol); 104 }); 105 mouseDrag(slideWrapper); 106 touchDrag(slideWrapper); 107 108 function timedCount() 109 { 110 if(direction=='left'){ 111 turnleft(); 112 }if(direction=='right'){ 113 turnright(); 114 } 115 clearTimeout(st); 116 st=setTimeout(function(){ 117 timedCount(); 118 },timecontrol); 119 } 120 121 function stopCount() 122 { 123 if (st !=""){ 124 clearTimeout(st); 125 } 126 } 127 128 function stopAll() 129 { 130 stopCount(); 131 clearTimeout(sts); 132 slideImgWrapper.stop(true); 133 } 134 135 function navclick(navnum){ 136 circlewrapper.children('canvas:eq('+navnum+')').click(function(e){ 137 scideimgskip(navnum+1); 138 }) 139 } 140 141 function scideimgskip(imgnum){ 142 stopAll(); 143 turnleftwidth = imgnum*slideWrapperWidth; 144 slideImgWrapper.stop(true).animate({left:-turnleftwidth},animatetime,function(){ 145 var imgnum = turnleftwidth/slideWrapperWidth-1; 146 if(imgnum==0){ 147 imgnum=imgcountrealy; 148 } 149 if((imgnum-imgcountrealy)==0){ 150 imgnum=0; 151 } 152 canvacircle(imgnum); 153 }); 154 } 155 156 function canvacircle(canvanum){ 157 circles.attr("width",circler); 158 circles.attr("height",circler); 159 for(var i=0;i<circles.length;i++){ 160 if(canvassuport){ 161 var navCanvas=circles[i]; 162 var cxt=navCanvas.getContext("2d"); 163 if(i==canvanum){ 164 cxt.fillStyle="#0182D7"; 165 }else{ 166 cxt.fillStyle="#ddd"; 167 } 168 cxt.arc(circler*0.5,circler*0.5,circler*0.5,0,Math.PI*2,true); 169 cxt.closePath(); 170 cxt.fill(); 171 }else{ 172 circles.css("background","#ddd"); 173 circlewrapper.children('canvas:eq('+canvanum+')').css("background","#0182D7"); 174 } 175 176 } 177 } 178 179 var turnleftwidth = slideWrapperWidth; 180 function turnleft(){ 181 turnleftwidth = turnleftwidth+slideWrapperWidth; 182 if(turnleftwidth>(imgcount-2)*slideWrapperWidth){ 183 slideImgWrapper.css({'left':0}); 184 turnleftwidth = slideWrapperWidth; 185 } 186 slideImgWrapper.stop(true).animate({left:-turnleftwidth},animatetime,function(){ 187 var imgnum = turnleftwidth/slideWrapperWidth-1; 188 if(imgnum==0){ 189 imgnum=imgcountrealy; 190 } 191 if((imgnum-imgcountrealy)==0){ 192 imgnum=0; 193 } 194 canvacircle(imgnum); 195 }); 196 } 197 function turnright(){ 198 turnleftwidth = turnleftwidth-slideWrapperWidth; 199 if(turnleftwidth==0){ 200 slideImgWrapper.css({'left':-slideWrapperWidth*(imgcount-1)}); 201 turnleftwidth = slideWrapperWidth*(imgcount-2); 202 } 203 slideImgWrapper.stop(true).animate({left:-turnleftwidth},animatetime,function(){ 204 var imgnum = turnleftwidth/slideWrapperWidth-1; 205 if(imgnum==0){ 206 imgnum=imgcountrealy; 207 } 208 if((imgnum-imgcountrealy)==0){ 209 imgnum=0; 210 } 211 canvacircle(imgnum); 212 }); 213 } 214 215 var distanceX=0; 216 217 function toLeft(){ 218 if(turnleftwidth>(imgcount-3)*slideWrapperWidth){ 219 slideImgWrapper.css({"left":distanceX}); 220 turnleftwidth = 0; 221 } 222 turnleft(); 223 sts = setTimeout(function(){ 224 timedCount(); 225 },timecontrol); 226 } 227 228 function toRight(){ 229 if(turnleftwidth==slideWrapperWidth){ 230 slideImgWrapper.css({'left':-slideWrapperWidth*(imgcount-1)+distanceX}); 231 turnleftwidth = slideWrapperWidth*(imgcount-1); 232 } 233 turnright(); 234 sts = setTimeout(function(){ 235 timedCount(); 236 },timecontrol); 237 } 238 239 function mouseDrag($element) { 240 var eventEle = $element; 241 var stx = etx = curX = 0; 242 var MAction = false; 243 var ahrefs = []; 244 for(var i=0;i<slideAs.length;i++){ 245 if(typeof(slideImgWrapper.children('a:eq('+i+')').attr('href'))!=='undefined'){ 246 ahrefs.push(slideImgWrapper.children('a:eq('+i+')').attr('href')); 247 } 248 } 249 eventEle.mouseover(function(){ 250 for(var i=0;i<slideAs.length;i++){ 251 if(typeof(slideImgWrapper.children('a:eq('+i+')').attr('href'))!=='undefined'){ 252 slideImgWrapper.children('a:eq('+i+')').attr('href',ahrefs[i]); 253 } 254 } 255 }); 256 eventEle.mousemove(function(event){ 257 if(MAction){ 258 var changeX = event.pageX-stx; 259 slideImgWrapper.css({"left":-turnleftwidth+changeX}); 260 distanceX = changeX; 261 } 262 event.preventDefault(); 263 }).mousedown(function(event){ 264 stopAll(); 265 MAction = true; 266 stx = event.pageX; 267 event.preventDefault(); 268 }); 269 eventEle.mouseup(function(event){ 270 etx = event.pageX; 271 curX = etx-stx; 272 MAction = false; 273 if(curX>5){ 274 slideAs.attr("href","javascript:void(0)"); 275 toRight(); 276 } 277 if(curX<-5){ 278 slideAs.attr("href","javascript:void(0)"); 279 toLeft(); 280 } 281 event.preventDefault(); 282 }); 283 } 284 285 function touchDrag($element) { 286 var gundongX = 0; 287 var gundongY = 0; 288 var moveEle = $element; 289 var stx = sty = etx = ety = curX = curY = 0; 290 291 var ImgWidth_arr = []; 292 for (var i = 0; i < imgcount; i++) { 293 ImgWidth_arr.push(i * slideWrapperWidth); 294 } 295 296 moveEle.on("touchstart", function(event) { //touchstart 297 stopAll(); 298 var event = event.originalEvent; 299 gundongX = 0; 300 gundongY = 0; 301 // 元素当前位置 302 etx = parseInt(getT3d(moveEle, "x")); 303 ety = parseInt(getT3d(moveEle, "y")); 304 // 手指位置 305 stx = event.touches[0].pageX; 306 sty = event.touches[0].pageY; 307 }); 308 moveEle.on("touchmove", function(event) { 309 var event = event.originalEvent; 310 // 防止拖动页面 311 event.preventDefault(); 312 313 // 手指位置 减去 元素当前位置 就是 要移动的距离 314 gundongX = event.touches[0].pageX - stx; 315 gundongY = event.touches[0].pageY - sty; 316 317 // 目标位置 就是 要移动的距离 加上 元素当前位置 318 curX = gundongX + etx; 319 curY = gundongY + ety; 320 slideImgWrapper.css({"left":-turnleftwidth+curX}); 321 distanceX = curX; 322 }); 323 moveEle.on("touchend", function(event) { 324 //alert(gundongX); 325 if(Math.abs(gundongX)>5){ 326 event.preventDefault(); 327 328 // 手指接触屏幕的位置 329 var oriX = etx; 330 var oriY = ety; 331 // 手指离开屏幕的位置 332 etx = curX; 333 ety = curY; 334 var slidePosition = 0; 335 for (var i = 0; i < imgcount - 1; i++) { 336 if (Math.abs(etx) > ImgWidth_arr[i]) { 337 338 if (oriX > etx) { 339 // 左滑 340 toLeft(); 341 } else { 342 // 右滑 343 toRight(); 344 } 345 } 346 } 347 } 348 349 }); 350 351 function getT3d(elem, ename) { 352 var elem = elem[0]; 353 var str1 = elem.style.webkitTransform; 354 if (str1 == "") return "0"; 355 str1 = str1.replace("translate3d(", ""); 356 str1 = str1.replace(")", ""); 357 var carr = str1.split(","); 358 359 if (ename == "x") return carr[0]; 360 else if (ename == "y") return carr[1]; 361 else if (ename == "z") return carr[2]; 362 else return ""; 363 } 364 } 365 }); 366 }; 367 })(jQuery);
3.整体思路
a.创建显示窗口,显示容器大小位置自定义;
b.创立图片容器,通过改变图片容器的位置来改变显示窗口中展示的图片。
难点在于如何改变图片容器位置来显示需要的图片和无缝轮播。可以想象一下以前的胶带式电影,如果放映速度慢下来就跟图片轮播类似了。所以可以把图片容器看成胶带,图片就是其中的一帧图像,显示窗口就是电影屏幕。
如果以一个显示窗口的宽为一个单位,我们把图片水平排放在图片容器中,每张图片恰好占用一个显示窗口的宽高,有多少张图片,图片容器的宽就为多少个单位宽。然后使图片向左移动一个单位的距离,就可以展示下一张图片了。为了使图片展示效果更美观,我用了jquery的动画效果animate()来改变图片容器的位置。如果设定间隔多少时间图片容器左移一个单位,达到最后一张图片时,图片容器恢复到原始位置,进行下一轮移动。这样,就可以实现图片的水平轮播了。至于无缝轮播,我采取的做法是把前两张图片复制一份,放到所有图片的最后面。首次显示第二张图片,当移动到倒数第二张图片时,在下一次移动时先改变容器的left,让第一张图片被显示出来,因为这个动作很快,看起来好像并没有进行任何操作(达到欺骗眼球的效果,呵呵),然后使用animate显示第二张图片,在视觉上实现无缝轮播。从第二张图片右移时也与此类似。这样做的主要目的是为了确保左右都有能被展示的图片,确保不会显示空白,以实现无缝切换。这里,会用到的主要知识点有:
(1).setTimeout和clearTimeout:
用于延迟执行、循环执行和终止循环。setTimeout延迟执行确定图片的显示时间,循环执行为了自动展示下一张图片。当需要停止轮播的时候就可以用clearTimeout来终止循环了。关于setTimeout和clearTimeout的具体用法,可以参照代码,这里就不再赘述了。
(2).jquery选择器:
无需多说,所有jquery动作的基础。
(3).jquery操作css:
为了保证使用的简单性,只需确定显示容器的样式(需确保overflow:hidden;插件中已设置),其子元素包括图片容器(这里只支持div标签slideWrapper.children('div:eq(0)'))和图片及其链接的样式都在插件中确定,包括:保证图片水平排列,大小恰好为显示窗口的大小;确定图片容器的大小恰好容下所有图片。
(4).jquery添加元素insertAfter:
复制前两张图片到所有图片的最后面$((slideImgWrapper.html().split("/div>")[0]+"/div>"+slideImgWrapper.html().split("/div>")[1]+"/div>")).insertAfter(slideAWra.last())。
(5).jquery动画效果animate:
1 var turnleftwidth = slideWrapperWidth; 2 function turnleft(){ 3 turnleftwidth = turnleftwidth+slideWrapperWidth; 4 if(turnleftwidth>(imgcount-2)*slideWrapperWidth){ 5 slideImgWrapper.css({'left':0}); 6 turnleftwidth = slideWrapperWidth; 7 } 8 slideImgWrapper.stop(true).animate({left:-turnleftwidth},animatetime); 18 } 19 function turnright(){ 20 turnleftwidth = turnleftwidth-slideWrapperWidth; 21 if(turnleftwidth==0){ 22 slideImgWrapper.css({'left':-slideWrapperWidth*(imgcount-1)}); 23 turnleftwidth = slideWrapperWidth*(imgcount-2); 24 } 25 slideImgWrapper.stop(true).animate({left:-turnleftwidth},animatetime); 35 }
其中imgcount为进行图片复制后图片的元素个数,slideWrapperWidth为显示窗口的宽。 turnleft()用于无缝左移,turnright()用于无缝右移。
现在来说说触屏支持。主要是用了jquery的on方法监听touch事件。具体怎么写,无非就是设置初始触摸位置,然后监听滑动的位置,根据水平滑动的具体实时改变图片容器的left,实现拖拽的效果。滑动结束后根据结束时的位置与初始位置的差判断左滑还是右滑,再来改变图片容器的left,使图片正好居中显示。跟PC端的鼠标拖拽类似。说到鼠标拖拽,不得不喷俩句。一般拖拽元素,基本都是mousedown、mousemove、mouseup三部曲。但是碰上图片就头疼了,特别是这图片的父亲还是a标签。mousedown没问题,mousemove能一个三五步,mousemove出问题mouseup也就失效了。弄了半天,终于通过 event.preventDefault()实现了对大部分浏览器的支持。废话不多说,让大家一起纠结下。
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml" lang="UTF-8"> 3 <head> 4 <meta charset="UTF-8"> 5 <style> 6 body{ 7 overflow-x:hidden; 8 margin:0 auto; 9 padding:0; 10 width: 100%; 11 height: 100%; 12 } 13 .aWrapper{ 14 position: relative; 15 margin:0 auto; 16 width:400px; 17 height:600px; 18 overflow:hidden; 19 //float:left; 20 } 21 .testa{ 22 position: relative; 23 margin:0 auto; 24 width:400px; 25 height:600px; 26 //float:left; 27 } 28 .testimg{ 29 position: absolute; 30 width:400px; 31 height:600px; 32 z-index:0; 33 } 34 .imgmask{ 35 position: relative; 36 width:400px; 37 height:600px; 38 z-index:1; 39 opacity:0.2; 40 background:#333; 41 } 42 </style> 43 </head> 44 <body> 45 <div class="aWrapper"> 46 <a href="http://www.baidu.com" class="testa"> 47 <img src="http://sandbox.runjs.cn/uploads/rs/215/auboqjjr/1 (1).jpg" class="testimg"/> 48 <div class='imgmask'></div> 49 </a> 50 </div> 51 </body> 52 </html>
上面测试页面只有在Safari内核的浏览器(如Chrome)下才能够拖拽的,而且当图片遮罩层的position设置为absolute或者a标签设置float时都会失效,实在是搞不明白。而且要说一下可以很容易的阻止事件向上冒泡,有什么办法触发父元素事件是不要触发子元素的该事件么?还是以上面的测试页面为例,要拖拽aWrapper的div,就要设置该元素的mousedown、mousemove、mouseup事件。但是这里有一个问题,在mousedown和mouseup之后会触发click事件,导致触发子元素a标签的click而跳转页面。如果仅仅在mouseup时设置a标签的click返回false,那么这个链接就永远失效了。具体处理方法,大家还是看代码吧,反正被我搞得复杂得很,说多了都是泪。
最后说说这个导航栏,就是下面的方方圈圈,用来显示第几张和点击跳转的。如果直接在页面上编写,问题倒还简单。在JS中就复杂多了。为了确保插件的独立性,除了对jquery的依赖外,我不想它有任何其他依赖。所以一般用图片来表示状态的方法就行不通了。我用的是html5的canvas绘制圆形。如果浏览器不支持或者担心浏览器消耗的话,可以关闭掉,直接用背景色表示,只是只能为方块块了。反正我觉得我自己弄的既不美观又不优雅,大家就当参照吧。