最近在做手机上的web app,很多场景需要使用到类似tab切换和图片轮播的东西,需要支持手势和鼠标点击的,于是就基于jquery弄了一个,每一个参数有详细的说明,目前只添加了scroll和none的效果,scroll即滑动的效果,可以支持x和y方向的滑动;none就是没有任何效果,只是显示和隐藏,后续需要添加其他效果再做扩展,写的有点水,呵呵,在此留笔,防止丢失。更新后的版本,只支持单个图轮播。
更新内容:将之前使用的用left控制动画修改为使用css3的-webkit-transform来控制动画,流畅度大大提升,另外使用translate3d和translateX来控制图片的位移。如果不支持transform则会使用原来的left来控制图片的位移。
Demo Address:http://kardel.sinaapp.com/qqbuy/1.html
检测浏览器支持某属性的方法:
// 检查是否支持css属性 var propertySupport = (function () { var element = document.createElement("i"); var eleStyle = element.style; var prefix = ' -o- -moz- -ms- -webkit- '.split(' '); var domPrefs = 'Webkit Moz O ms'.split(' '); return { hasProperty:function (prop) { var camel = prop.charAt(0).toUpperCase() + prop.substr(1), props = (prop + ' ' + domPrefs.join(camel + ' ') + camel).split(' '); for (var i in props) { if (eleStyle[props[i]] !== undefined) { return true; } } return false; }, isSupport3D:function () { if ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()) { return true; } var css = ['perspectiveProperty', 'MozPerspective', 'OPerspective', 'msPerspective'] for (var p in css) { if (css[p] in eleStyle) { return true; } } return false; } } })();
实现代码:
1 $.fn.loopSlider = function (option) { 2 function config(opt) { 3 var setting = { 4 // 默认显示的顺序 5 initIndex: 1, 6 // 加在状态节点上的样式 7 className: "current", 8 // 轮播方向,默认为x轴方向轮播 9 direct: "x", 10 // 上一张按钮 11 prevBtn: "", 12 // 下一张按钮 13 nextBtn: "", 14 // 上下翻页按钮禁用的样式 15 btnDisable: "disable", 16 // 按钮按下的样式 17 btnTouchClass: "", 18 // 自动轮播 19 auto: false, 20 // 自动轮播时间间隔 21 timeFlag: 4000, 22 // 轮播效果时间 23 scrollTime: 350, 24 // 轮播效果 25 effect: "scroll", 26 // 在只有一个轮播元素的时候是否隐藏滑动按钮 27 hideBtn: true, 28 // 是否循环轮播 29 cycle: true, 30 // 轮播的内容区的容器路径 31 contentContainer: "", 32 // 轮播的内容区的节点 33 contentChildTag: "", 34 // 标题轮播区域的容器路径 35 titleContainer: "", 36 // 标题轮播区域的节点 37 titleChildTag: "", 38 // 隐藏的图片链接属性 39 imgAttr: "back_src", 40 // 轮播的内容区的数组 41 cont: [], 42 // 轮播的标题区的数组 43 tabs: [], 44 // 显示数量 45 showNum: 1, 46 // 元素间的margin值 47 gapWidth: 0, 48 // 轮播回调函数,每次轮播调用,参数为当前轮播的序号 49 callback: function () { 50 return true; 51 } 52 } 53 $.extend(setting, opt); 54 return setting; 55 } 56 57 var boss = $(this); 58 var nodeList = []; 59 boss.each(function (index) { 60 nodeList[index] = { 61 node: $(this), 62 setting: config(option) 63 } 64 handleScroll(nodeList[index]); 65 }); 66 return { 67 get: function (index) { 68 if (nodeList[index]) { 69 return nodeList[index]; 70 } else { 71 return false; 72 } 73 } 74 }; 75 76 function handleScroll(obj) { 77 var setting = obj.setting; 78 // 当前页 79 setting.current = 0; 80 // 定时器 81 setting.ptr = ""; 82 var node = obj.node; 83 setting.imgloadedNum = 0; 84 // 初始化当前调用类型的函数 85 setting.currentMethod = function () { 86 return true; 87 } 88 // 如果不是第一个元素先轮播 89 if (setting.initIndex != 1) { 90 setting.current = setting.initIndex - 1; 91 } 92 // 获取轮播的节点列表 93 var childList = node.find(setting.contentContainer + " " + setting.contentChildTag); 94 // 获取轮播标题节点列表 95 var titleList = node.find(setting.titleContainer + " " + setting.titleChildTag); 96 // 上下箭头 97 var nextBtn = node.find(setting.nextBtn); 98 var prevBtn = node.find(setting.prevBtn); 99 // 保存内容区每一个轮播节点 100 setting.cont = childList; 101 // 保存标题的轮播节点 102 setting.tabs = titleList; 103 // 如果没有需要轮播的内容,直接返回 104 if (setting.cont.length <= 1) { 105 if (setting.hideBtn) { 106 prevBtn.hide(); 107 nextBtn.hide(); 108 } 109 return; 110 } 111 var withtitle = setting.titleContainer && setting.tabs.length > 0; 112 // 给内容区和标题区设置index属性 113 childList.each(function (index) { 114 $(this).attr("index", index); 115 // 设置了title,但是内容不足,补全 116 if (withtitle && !setting.tabs[index]) { 117 var first = titleList.eq(0); 118 var cloneNode = first.clone(); 119 first.parent().append(cloneNode); 120 cloneNode.attr("index", index); 121 } 122 titleList.eq(index).attr("index", index); 123 }); 124 // 如果有添加过状态节点,则重新更新一下状态节点容器 125 if (withtitle && setting.tabs.length != setting.cont.length) { 126 titleList = node.find(setting.titleContainer + " " + setting.titleChildTag); 127 setting.tabs = titleList; 128 } 129 // 长度 130 var counts = childList.length; 131 // 轮播容器的父节点 132 var childParent = childList.parent(); 133 var titleParent = titleList.parent(); 134 if (childList.length < setting.initIndex) { 135 setting.current = 0; 136 } 137 // 初始化 138 doInit(); 139 /** 140 * 处理无效果的切换 141 */ 142 var doScrollNone = { 143 process: function () { 144 var i = setting.current; 145 loadImage(); 146 childList.eq(i).css("display", "block").siblings().css("display", "none"); 147 titleList.eq(i).addClass(setting.className).siblings().removeClass(setting.className); 148 // 调用回调函数 149 setting.callback(setting.current); 150 if (setting.auto) { 151 processAuto(); 152 } 153 }, 154 init: function () { 155 setting.currentMethod = doScrollNone; 156 loadImage(); 157 bindEvent(); 158 // 自动轮播 159 if (setting.auto) { 160 processAuto(); 161 } 162 // 初始化的时候调用回调函数 163 setting.callback(setting.current); 164 } 165 }; 166 var doScrollXY = { 167 init: function () { 168 // 轮播元素的宽度 169 setting.c_width = childList.width(); 170 // 轮播元素的高度 171 setting.c_height = childList.height(); 172 // 克隆第一个元素到最后 173 if (setting.cycle && childList.length > 1) { 174 var firstNode = childList.eq(0).clone(true); 175 var lastNode = childList.eq(counts - 1).clone(true); 176 if (setting.direct == "x") { 177 lastNode.css({ 178 position: 'relative', 179 left: -setting.c_width * (counts + 2) 180 }); 181 } else { 182 lastNode.css({ 183 position: 'relative', 184 top: -setting.c_height * (counts + 2) 185 }); 186 } 187 firstNode.attr("index", 0); 188 firstNode.appendTo(childParent); 189 lastNode.attr("index", counts - 1); 190 lastNode.appendTo(childParent); 191 // 更新列表信息 192 childList = childParent.children(); 193 } 194 // x轴方向轮播 195 if (setting.direct == "x") { 196 childParent.width(setting.c_width * (setting.cycle ? counts + 2 : counts)); 197 if (setting.transform) { 198 childParent.css({ 199 "-webkit-backface-visibility": "hidden", 200 "-webkit-transform": "translateX(" + (-setting.c_width * (setting.current)) + "px)" 201 }); 202 } else { 203 childParent.css('left', - setting.c_width * setting.current); 204 } 205 } else { 206 childParent.width(setting.c_width); 207 childParent.height(setting.c_height * (setting.cycle ? counts + 2 : counts)); 208 if (setting.transform) { 209 childParent.css({ 210 "-webkit-backface-visibility": "hidden", 211 "-webkit-transform": "translateY(" + (-setting.c_height * (setting.current)) + "px)" 212 }); 213 } else { 214 childParent.css('top', - setting.c_height * setting.current); 215 } 216 } 217 titleList.eq(setting.current).addClass(setting.className).siblings().removeClass(setting.className); 218 setting.currentMethod = doScrollXY; 219 // 初始化的时候也调用回调函数 220 setting.callback(setting.current); 221 // 绑定事件 222 bindEvent(); 223 // 自动轮播 224 if (setting.auto) { 225 processAuto(); 226 } 227 }, 228 process: function () { 229 var index = setting.current; 230 var _this = this; 231 loadImage(); 232 if (setting.direct == "x") { 233 this.processDirect("left", setting.c_width, index); 234 } else { 235 this.processDirect("top", setting.c_height, index); 236 } 237 if (index == counts) { 238 index = 0; 239 } else if (index == -1) { 240 index = counts - 1; 241 } 242 this.processEnd(index); 243 }, 244 processDirect: function (direct, value, index) { 245 var _this = this; 246 var trans = direct == "left" ? "translateX" : "translateY"; 247 if (setting.transform) { 248 childParent.css({ 249 "-webkit-transform": trans + "(" + (-value * index) + "px)", 250 "-webkit-transition": setting.scrollTime / 1000 + "s ease-out", 251 "-webkit-backface-visibility": "hidden" 252 }); 253 } else if (setting.transition) { 254 childParent.css({ 255 direct: -value * index, 256 "-webkit-transition": direct + " " + setting.scrollTime / 1000 + "s ease-out", 257 "-webkit-backface-visibility": "hidden" 258 }); 259 } else { 260 childParent.animate({ 261 direct: -value * index 262 }, setting.scrollTime); 263 } 264 setTimeout(function () { 265 if (setting.is3D) { 266 childParent.css({ 267 "-webkit-transform": (direct == "left" ? "translate3d(" + (-value * index) + "px,0px,0px)" : "translate3d(0px," + (-value * index) + "px,0px)"), 268 "-webkit-transition": "" 269 }); 270 } 271 if (setting.current == counts && setting.cycle) { 272 _this.moveElement(); 273 } else if (setting.current == -1) { 274 if (setting.is3D) { 275 childParent.css({ 276 "-webkit-transform": (direct == "left" ? "translate3d(" + (-value * (counts - 1)) + "px,0px,0px)" : "translate3d(0px," + (-value * (counts - 1)) + "px,0px)"), 277 "-webkit-transition": "" 278 }); 279 } else { 280 childParent.css({ 281 direct: -value * (counts - 1) 282 }); 283 } 284 } 285 if (setting.auto) { 286 //非循环并且到了最后一个,停止定时器 287 !setting.cycle && setting.current == counts - 1 ? "" : processAuto(); 288 } 289 }, setting.scrollTime); 290 }, 291 processEnd: function (index) { 292 this.updateBtnStatus(index); 293 // 调用回调函数 294 setting.callback(index); 295 titleList.eq(index).addClass(setting.className).siblings().removeClass(setting.className); 296 }, 297 updateBtnStatus: function (i) { 298 if (i == 0 && !setting.cycle) { 299 setting.prevBtn && prevBtn.addClass(setting.btnDisable); 300 } else { 301 setting.prevBtn && prevBtn.removeClass(setting.btnDisable); 302 } 303 if (i == counts - 1 && !setting.cycle) { 304 setting.nextBtn && nextBtn.addClass(setting.btnDisable); 305 } else { 306 setting.nextBtn && nextBtn.removeClass(setting.btnDisable); 307 } 308 }, 309 moveElement: function () { 310 var lastNode = childList.eq(counts + 1); 311 for (var i = 1; i < childList.length - 2; i++) { 312 childList.eq(i).remove().insertBefore(lastNode); 313 } 314 childList.eq(0).remove().insertBefore(lastNode); 315 childParent.css({ 316 "-webkit-transform": "translate3d(0px,0px,0px)", 317 "-webkit-transition": "" 318 }); 319 } 320 }; 321 var touchEvent = { 322 startX: 0, 323 startY: 0, 324 currentPosition: 0, 325 start: function (event) { 326 if (event.changedTouches.length == 0) { 327 return; 328 } 329 var po = touchEvent.getPosition(event); 330 touchEvent.startX = po.x; 331 touchEvent.startY = po.y; 332 //手势滑动方向 333 var moveDirect = "add"; 334 // 计算当前值的乘积因子 335 var num = setting.current == -1 ? counts - 1 : setting.current != counts ? setting.current : 0; 336 // 当前显示元素所处的位置 337 touchEvent.currentPosition = -num * (setting.direct == "x" ? setting.c_width : setting.c_height); 338 // 清除定时器 339 if (setting.ptr) { 340 clearInterval(setting.ptr); 341 setting.ptr = null; 342 } 343 childParent[0].ontouchmove = touchEvent.move; 344 }, 345 move: function (moveEvent) { 346 var movePo = touchEvent.getPosition(moveEvent); 347 var moveX = movePo.x; 348 var moveY = movePo.y; 349 if (setting.direct == 'x') { 350 var x = moveX - touchEvent.startX; 351 // 横坐标和纵坐标的差值超过10个像素,才给滑动 352 if (Math.abs(x) > Math.abs(moveY - touchEvent.startY)) { 353 // 阻止默认的事件 354 moveEvent.preventDefault(); 355 if (setting.is3D) { 356 childParent.css({ 357 "-webkit-transform": "translate3d(" + (parseFloat(touchEvent.currentPosition) + x) + "px,0px,0px)" 358 }); 359 } else if (setting.transform) { 360 childParent.css({ 361 "-webkit-transform": "translateX(" + (parseFloat(touchEvent.currentPosition) + x) + "px)" 362 }); 363 } else { 364 childParent.css({ 365 "left": parseFloat(touchEvent.currentPosition) + x 366 }); 367 } 368 moveDirect = x > 0 ? "sub" : "add"; 369 } else { 370 childParent[0].ontouchmove = null; 371 return; 372 } 373 } else { 374 // Y轴方向滚动 375 moveEvent.preventDefault(); 376 var y = moveY - touchEvent.startY; 377 if (setting.is3D) { 378 childParent.css({ 379 "-webkit-transform": "translate3d(0px," + (parseFloat(touchEvent.currentPosition) + y) + "px,0px)" 380 }); 381 } else if (setting.transform) { 382 childParent.css({ 383 "-webkit-transform": "translateY(" + (parseFloat(touchEvent.currentPosition) + y) + "px)" 384 }); 385 } else { 386 childParent.css({ 387 "top": parseFloat(touchEvent.currentPosition) + y 388 }); 389 } 390 moveDirect = y > 0 ? "sub" : "add"; 391 } 392 childParent[0].ontouchend = touchEvent.end; 393 }, 394 end: function () { 395 //根据手指移动的方向,判断下一个要显示的节点序号 396 if (moveDirect == "add") { 397 setNext(); 398 } else { 399 setPrev(); 400 } 401 // 调用对应的处理函数 402 setting.currentMethod.process(); 403 childParent[0].ontouchend = null; 404 childParent[0].ontouchmove = null; 405 }, 406 getPosition: function (evt) { 407 var touch = evt.changedTouches ? evt.changedTouches[0] : evt; 408 return { 409 x: touch.clientX, 410 y: touch.clientY 411 } 412 } 413 } 414 switch (setting.effect) { 415 case "none": 416 doScrollNone.init(); 417 break; 418 case "scroll": 419 doScrollXY.init(); 420 break; 421 } 422 423 // 一些初始化操作 424 function doInit() { 425 childParent.css("position", "relative"); 426 if (!setting.cycle) { 427 prevBtn.removeClass(setting.btnDisable); 428 nextBtn.removeClass(setting.btnDisable); 429 if (setting.current == 0) { 430 prevBtn.addClass(setting.btnDisable); 431 } 432 if (setting.current == counts - 1) { 433 nextBtn.addClass(setting.btnDisable); 434 } 435 } 436 // 判断是否支持几个属性 437 setting.transform = propertySupport.hasProperty("transform"); 438 setting.transition = propertySupport.hasProperty("transition"); 439 setting.is3D = propertySupport.isSupport3D(); 440 if (setting.effect != "none") { 441 setting.current == 0 ? initImage() : loadImage(); 442 } 443 } 444 445 // 加载图片,先加载第一个和最后一个图 446 function initImage() { 447 var firstNode = childList.first(); 448 var lastNode = childList.last(); 449 getImg(firstNode.find("img")); 450 getImg(lastNode.find("img")); 451 } 452 453 function getImg(img) { 454 if (img.attr(setting.imgAttr)) { 455 img.attr("src", img.attr(setting.imgAttr)); 456 img.removeAttr(setting.imgAttr); 457 } 458 } 459 460 function loadImage() { 461 var curNode = childList.eq(setting.current); 462 getImg(curNode.find("img")); 463 } 464 465 function setPrev() { 466 var cur = parseInt(setting.current, 10); 467 var value = 0; 468 switch (cur) { 469 case 0: 470 value = setting.cycle ? (setting.effect == "none" ? counts - 1 : -1) : 0; 471 break; 472 case -1: 473 value = counts - 2; 474 break; 475 case counts: 476 value = -1; 477 break; 478 default: 479 value = cur - 1; 480 break; 481 } 482 setting.current = value; 483 if (setting.ptr) { 484 clearInterval(setting.ptr); 485 setting.ptr = null; 486 } 487 } 488 489 function setNext() { 490 var cur = parseInt(setting.current, 10); 491 var value = 1; 492 switch (cur) { 493 case counts: 494 value = 1; 495 break; 496 case -1: 497 value = counts; 498 break; 499 case counts - 1: 500 value = setting.cycle ? (setting.effect == "none" ? 0 : counts) : cur; 501 break; 502 default: 503 value = cur + 1; 504 break; 505 } 506 setting.current = value; 507 if (setting.ptr) { 508 clearInterval(setting.ptr); 509 setting.ptr = null; 510 } 511 } 512 /** 513 * 绑定轮播事件 514 */ 515 function bindEvent() { 516 setting.nextBtn && nextBtn.bind("click", function () { 517 // 如果按钮已经被禁用 518 if ($(this).hasClass(setting.btnDisable)) { 519 return; 520 } 521 setNext(); 522 $(this).addClass(setting.btnTouchClass); 523 setting.currentMethod.process(); 524 return false; 525 }); 526 setting.prevBtn && prevBtn.bind("click", function () { 527 if ($(this).hasClass(setting.btnDisable)) { 528 return; 529 } 530 setPrev(); 531 $(this).addClass(setting.btnTouchClass); 532 setting.currentMethod.process(); 533 return false; 534 }); 535 childParent[0].ontouchstart = touchEvent.start; 536 } 537 /** 538 * 自动轮播 539 */ 540 function processAuto() { 541 if (setting.ptr) { 542 clearInterval(setting.ptr); 543 setting.ptr = null; 544 } 545 // 设置轮播定时器 546 setting.ptr = setInterval(function () { 547 if (setting.current == counts) { 548 setting.current = 1; 549 } else if (setting.current == counts - 1) { 550 setting.current = setting.cycle ? (setting.effect == "none" ? 0 : counts) : counts - 1; 551 if (!setting.cycle && setting.ptr) { 552 clearInterval(setting.ptr); 553 setting.ptr = null; 554 } 555 } else { 556 setting.current = setting.current + 1; 557 } 558 setting.currentMethod.process(); 559 }, setting.timeFlag); 560 } 561 562 obj.setIndex = function (index) { 563 if (index < 0) { 564 index = 0; 565 } else if (index >= counts) { 566 index = counts - 1; 567 } 568 setting.current = index; 569 setting.currentMethod.process(); 570 } 571 } 572 }