touchslider.dev.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /**
  2. * TouchSlider v1.2.4
  3. * By qiqiboy, http://www.qiqiboy.com, http://weibo.com/qiqiboy, 2012/12/05
  4. */
  5. (function(window, undefined){
  6. "use strict";
  7. var hasTouch=("createTouch" in document) || ('ontouchstart' in window),
  8. testStyle=document.createElement('div').style,
  9. testVendor=(function(){
  10. var cases={
  11. 'OTransform':['-o-','otransitionend'],
  12. 'WebkitTransform':['-webkit-','webkitTransitionEnd'],
  13. 'MozTransform':['-moz-','transitionend'],
  14. 'msTransform':['-ms-','MSTransitionEnd'],
  15. 'transform':['','transitionend']
  16. },prop;
  17. for(prop in cases){
  18. if(prop in testStyle)return cases[prop];
  19. }
  20. return false;
  21. })(),
  22. sg=[['width','left','right'],['height','top','bottom']],
  23. cssVendor=testVendor&&testVendor[0],
  24. toCase=function(str){
  25. return (str+'').replace(/^-ms-/, 'ms-').replace(/-([a-z]|[0-9])/ig, function(all, letter){
  26. return (letter+'').toUpperCase();
  27. });
  28. },
  29. testCSS=function(prop){
  30. var _prop=toCase(cssVendor+prop);
  31. return (prop in testStyle)&& prop || (_prop in testStyle)&& _prop;
  32. },
  33. parseArgs=function(arg,dft){
  34. for(var key in dft){
  35. if(typeof arg[key]=='undefined'){
  36. arg[key]=dft[key];
  37. }
  38. }
  39. return arg;
  40. },
  41. children=function(elem){
  42. var children=elem.children||elem.childNodes,
  43. _ret=[],i=0;
  44. for(;i<children.length;i++){
  45. if(children[i].nodeType===1){
  46. _ret.push(children[i]);
  47. }
  48. }
  49. return _ret;
  50. },
  51. each=function(arr,func){
  52. var i=0,j=arr.length;
  53. for(;i<j;i++){
  54. if(func.call(arr[i],i,arr[i])===false){
  55. break;
  56. }
  57. }
  58. },
  59. returnFalse=function(evt){
  60. evt=TouchSlider.fn.eventHook(evt);
  61. evt.preventDefault();
  62. },
  63. startEvent=hasTouch ? "touchstart" : "mousedown",
  64. moveEvent=hasTouch ? "touchmove" : "mousemove",
  65. endEvent=hasTouch ? "touchend" : "mouseup",
  66. transitionend=testVendor[1]||'',
  67. TouchSlider=function(id,cfg){
  68. if(!(this instanceof TouchSlider)){
  69. return new TouchSlider(id,cfg);
  70. }
  71. if(typeof id!='string' && !id.nodeType){
  72. cfg=id;
  73. id=cfg.id;
  74. }
  75. if(!id.nodeType){
  76. id=document.getElementById(id);
  77. }
  78. this.cfg=parseArgs(cfg||{},this._default);
  79. this.element=id;
  80. if(this.element){
  81. this.container=this.element.parentNode||document.body;
  82. this.setup();
  83. }
  84. }
  85. TouchSlider.fn=TouchSlider.prototype={
  86. //默认配置
  87. _default: {
  88. 'id':'slider', //幻灯容器的id
  89. 'begin':0,
  90. 'auto':true, //是否自动开始,负数表示非自动开始,0,1,2,3....表示自动开始以及从第几个开始
  91. 'speed':600, //动画效果持续时间 ms
  92. 'timeout':5000,//幻灯间隔时间 ms,
  93. 'direction':'left', //left right up down
  94. 'align':'center',
  95. 'fixWidth':true,
  96. 'mouseWheel':false,
  97. 'before':new Function,
  98. 'after':new Function
  99. },
  100. //设置OR获取节点样式
  101. css:function(elem,css){
  102. if(typeof css=='string'){
  103. var style=document.defaultView && document.defaultView.getComputedStyle && getComputedStyle(elem, null) || elem.currentStyle || elem.style || {};
  104. return style[toCase(css)];
  105. }else{
  106. var prop,
  107. propFix;
  108. for(prop in css){
  109. if(prop=='float'){
  110. propFix=("cssFloat" in testStyle) ? 'cssFloat' : 'styleFloat';
  111. }else{
  112. propFix=toCase(prop);
  113. }
  114. elem.style[propFix]=css[prop];
  115. }
  116. }
  117. },
  118. //绑定事件
  119. addListener:function(e, n, o, u){
  120. if(e.addEventListener){
  121. e.addEventListener(n, o, u);
  122. return true;
  123. } else if(e.attachEvent){
  124. e.attachEvent('on' + n, o);
  125. return true;
  126. }
  127. return false;
  128. },
  129. removeListener:function(e, n, o, u){
  130. if(e.addEventListener){
  131. e.removeEventListener(n, o, u);
  132. return true;
  133. } else if(e.attachEvent){
  134. e.detachEvent('on' + n, o);
  135. return true;
  136. }
  137. return false;
  138. },
  139. eventHook:function(origEvt){
  140. var evt={},
  141. props="changedTouches touches scale target view which clientX clientY fromElement offsetX offsetY pageX pageY toElement".split(" ");
  142. origEvt=origEvt||window.event;
  143. each(props,function(){
  144. evt[this]=origEvt[this];
  145. });
  146. evt.target=origEvt.target||origEvt.srcElement||document;
  147. if(evt.target.nodeType===3){
  148. evt.target=evt.target.parentNode;
  149. }
  150. evt.preventDefault=function(){
  151. origEvt.preventDefault && origEvt.preventDefault();
  152. evt.returnValue=origEvt.returnValue=false;
  153. }
  154. evt.stopPropagation=function(){
  155. origEvt.stopPropagation && origEvt.stopPropagation();
  156. evt.cancelBubble=origEvt.cancelBubble=true;
  157. }
  158. if(hasTouch&&evt.touches.length){
  159. evt.pageX=evt.touches.item(0).pageX;
  160. evt.pageY=evt.touches.item(0).pageY;
  161. }else if(typeof origEvt.pageX=='undefined'){
  162. var doc=document.documentElement,
  163. body=document.body;
  164. evt.pageX=origEvt.clientX+(doc&&doc.scrollLeft || body&&body.scrollLeft || 0)-(doc&&doc.clientLeft || body&&body.clientLeft || 0);
  165. evt.pageY=origEvt.clientY+(doc&&doc.scrollTop || body&&body.scrollTop || 0)-(doc&&doc.clientTop || body&&body.clientTop || 0);
  166. }
  167. evt.origEvent=origEvt;
  168. return evt;
  169. },
  170. //修正函数作用环境
  171. bind:function(func, obj){
  172. return function(){
  173. return func.apply(obj, arguments);
  174. }
  175. },
  176. //初始化
  177. setup: function(){
  178. this.slides=children(this.element);
  179. this.length=this.slides.length;
  180. this.cfg.timeout=parseInt(this.cfg.timeout);
  181. this.cfg.speed=parseInt(this.cfg.speed);
  182. this.cfg.begin=parseInt(this.cfg.begin);
  183. this.cfg.auto=!!this.cfg.auto;
  184. this.cfg.timeout=Math.max(this.cfg.timeout,this.cfg.speed);
  185. this.touching=!!hasTouch;
  186. this.css3transition=!!testVendor;
  187. this.index=this.cfg.begin<0||this.cfg.begin>=this.length ? 0 : this.cfg.begin;
  188. if(this.length<1)return false;
  189. switch(this.cfg.direction){
  190. case 'up':
  191. case 'down':this.direction=this.cfg.direction; this.vertical=1; break;
  192. case 'right':this.direction='right';
  193. default:this.direction=this.direction||'left'; this.vertical=0; break;
  194. }
  195. this.addListener(this.element,startEvent,this.bind(this._start,this),false);
  196. this.addListener(document,moveEvent,this.bind(this._move,this),false);
  197. this.addListener(document,endEvent,this.bind(this._end,this),false);
  198. this.addListener(document,'touchcancel',this.bind(this._end,this),false);
  199. this.addListener(this.element,transitionend,this.bind(this.transitionend,this),false);
  200. this.addListener(window,'resize',this.bind(function(){
  201. clearTimeout(this.resizeTimer);
  202. this.resizeTimer=setTimeout(this.bind(this.resize,this),100);
  203. },this),false);
  204. if(this.cfg.mouseWheel){
  205. this.addListener(this.element,'mousewheel',this.bind(this.mouseScroll,this),false);
  206. this.addListener(this.element,'DOMMouseScroll',this.bind(this.mouseScroll,this),false);
  207. }
  208. this.playing=this.cfg.auto;
  209. this.resize();
  210. },
  211. getSum:function(type,start,end){
  212. var sum=0,i=start,
  213. _type=toCase('-'+type);
  214. for(;i<end;i++){
  215. sum+=this['getOuter'+_type](this.slides[i]);
  216. }
  217. return sum;
  218. },
  219. getPos:function(type,index){
  220. var _type=toCase('-'+type),
  221. myWidth=this.getSum(type,index,index+1),
  222. sum=this.getSum(type,0,index)+this['getOuter'+_type](this.element)/2-this['get'+_type](this.element)/2;
  223. switch(this.cfg.align){
  224. case 'left':
  225. return -sum;
  226. case 'right':
  227. return this[type]-myWidth-sum;
  228. default:return (this[type]-myWidth)/2-sum;
  229. }
  230. },
  231. resize:function(){
  232. clearTimeout(this.aniTimer);
  233. var _this=this,css,type=sg[this.vertical][0],_type=toCase('-'+type),
  234. pst=this.css(this.container,'position');
  235. this.css(this.container,{'overflow':'hidden','visibility':'hidden','listStyle':'none','position':pst=='static'?'relative':pst});
  236. this[type]=this['get'+_type](this.container);
  237. css={float:this.vertical?'none':'left',display:'block'};
  238. each(this.slides,function(){
  239. if(_this.cfg.fixWidth){
  240. css[type]=_this[type]-_this['margin'+_type](this)-_this['padding'+_type](this)-_this['border'+_type](this)+'px';
  241. }
  242. _this.css(this,css);
  243. });
  244. this.total=this.getSum(type,0,this.length);
  245. css={position:'relative',overflow:'hidden'};
  246. css[cssVendor+'transition-duration']='0ms';
  247. css[type]=this.total+'px';
  248. css[sg[this.vertical][1]]=this.getPos(type,this.index)+'px';
  249. this.css(this.element,css);
  250. this.css(this.container,{'visibility':'visible'});
  251. this.playing && this.play();
  252. return this;
  253. },
  254. slide:function(index, speed){
  255. var direction=sg[this.vertical][1],
  256. type=sg[this.vertical][0],
  257. transition=testCSS('transition'),
  258. nowPos=parseFloat(this.css(this.element,direction))||0,
  259. endPos,css={},change,size=this.getSum(type,index,index+1);
  260. index=Math.min(Math.max(0,index),this.length-1);
  261. speed=typeof speed=='undefined' ? this.cfg.speed : parseInt(speed);
  262. endPos=this.getPos(type,index);
  263. change=endPos-nowPos, //变化量
  264. speed=Math.abs(change)<size?Math.ceil(Math.abs(change)/size*speed):speed;
  265. if(transition){
  266. css[transition]=direction+' ease '+speed+'ms';
  267. css[direction]=endPos+'px';
  268. this.css(this.element,css);
  269. }else{
  270. var _this=this,
  271. begin=0, //动画开始时间
  272. time=speed/10,//动画持续时间
  273. animate=function(t,b,c,d){ //缓动效果计算公式
  274. return -c * ((t=t/d-1)*t*t*t - 1) + b;
  275. },
  276. run=function(){
  277. if(begin<time){
  278. begin++;
  279. _this.element.style[direction]=Math.ceil(animate(begin,nowPos,change,time))+'px';
  280. _this.aniTimer=setTimeout(run,10);
  281. }else{
  282. _this.element.style[direction]=endPos+'px';
  283. _this.transitionend({propertyName:direction});
  284. }
  285. }
  286. clearTimeout(this.aniTimer);
  287. run();
  288. }
  289. this.cfg.before.call(this,index,this.slides[this.index]);
  290. this.index=index;
  291. return this;
  292. },
  293. play:function(){
  294. clearTimeout(this.timer);
  295. this.playing=true;
  296. this.timer=setTimeout(this.bind(function(){
  297. this.direction=='left'||this.direction=='up' ? this.next() : this.prev();
  298. },this), this.cfg.timeout);
  299. return this;
  300. },
  301. pause:function(){
  302. clearTimeout(this.timer);
  303. this.playing=false;
  304. return this;
  305. },
  306. stop:function(){
  307. this.pause();
  308. return this.slide(0);
  309. },
  310. prev:function(offset,sync){
  311. clearTimeout(this.timer);
  312. var index=this.index;
  313. offset=typeof offset == 'undefined'?offset=1:offset%this.length;
  314. index-=offset;
  315. if(sync===false){
  316. index=Math.max(index,0);
  317. }else{
  318. index=index<0?this.length+index:index;
  319. }
  320. return this.slide(index);
  321. },
  322. next:function(offset,sync){
  323. clearTimeout(this.timer);
  324. var index=this.index;
  325. if(typeof offset=='undefined')offset=1;
  326. index+=offset;
  327. if(sync===false){
  328. index=Math.min(index,this.length-1);
  329. }else{
  330. index%=this.length
  331. }
  332. return this.slide(index);
  333. },
  334. _start:function(evt){
  335. evt=this.eventHook(evt);
  336. if(!this.touching)evt.preventDefault();
  337. this.removeListener(this.element,'click',returnFalse);
  338. this.startPos=[evt.pageX,evt.pageY];
  339. this.element.style[toCase(cssVendor+'transition-duration')]='0ms';
  340. this.startTime=+new Date;
  341. this._pos=parseFloat(this.css(this.element,sg[this.vertical][1]))||0;
  342. },
  343. _move:function(evt){
  344. if(!this.startPos || evt.scale&&evt.scale!==1)return;
  345. evt=this.eventHook(evt);
  346. this.stopPos=[evt.pageX,evt.pageY];
  347. var direction=sg[this.vertical][1],
  348. type=sg[this.vertical][0],
  349. offset=this.stopPos[this.vertical]-this.startPos[this.vertical];
  350. if(this.scrolling || typeof this.scrolling=='undefined'&&Math.abs(offset)>=Math.abs(this.stopPos[1-this.vertical]-this.startPos[1-this.vertical])){
  351. evt.preventDefault();
  352. offset=offset/((!this.index&&offset>0 || this.index==this.length-1&&offset<0) ? (Math.abs(offset)/this[type]+1) : 1);
  353. this.element.style[direction]=this._pos+offset+'px';
  354. if(offset&&typeof this.scrolling=='undefined'){
  355. this.scrolling=true;//标记拖动(有效触摸)
  356. clearTimeout(this.timer);//暂停幻灯
  357. clearTimeout(this.aniTimer);//暂停动画
  358. }
  359. }else this.scrolling=false;
  360. },
  361. _end:function(evt){
  362. if(this.startPos){
  363. if(this.scrolling){
  364. var type=sg[this.vertical][0],
  365. direction=sg[this.vertical][1],
  366. offset=this.stopPos[this.vertical]-this.startPos[this.vertical],
  367. absOff=Math.abs(offset),
  368. sub=absOff/offset,
  369. myWidth,curPos,tarPos,
  370. next=this.index,off=0;
  371. this.addListener(this.element,'click',returnFalse);
  372. if(absOff>20){//有效移动距离
  373. curPos=parseFloat(this.css(this.element,sg[this.vertical][1]));
  374. do{
  375. if(next>=0 && next<this.length){
  376. tarPos=this.getPos(type,next);
  377. myWidth=this.getSum(type,next,next+1);
  378. }else{
  379. next+=sub;
  380. break;
  381. }
  382. }while(Math.abs(tarPos-curPos)>myWidth/2 && (next-=sub));
  383. off=Math.abs(next-this.index);
  384. if(!off && +new Date-this.startTime<250){
  385. off=1;
  386. }
  387. }
  388. offset>0?this.prev(off,false):this.next(off,false);
  389. this.playing && this.play();
  390. }
  391. delete this._pos;
  392. delete this.stopPos;
  393. delete this.startPos;
  394. delete this.scrolling;
  395. delete this.startTime;
  396. }
  397. },
  398. mouseScroll:function(evt){
  399. if(this.cfg.mouseWheel){
  400. evt=this.eventHook(evt);
  401. evt.preventDefault();
  402. var _e=evt.origEvent;
  403. var wheelDelta=_e.wheelDelta || _e.detail && _e.detail*-1 || 0,
  404. flag=wheelDelta/Math.abs(wheelDelta);
  405. wheelDelta>0?this.prev(1,false):this.next(1,false);
  406. }
  407. },
  408. transitionend:function(evt){
  409. if(evt.propertyName==sg[this.vertical][1]){
  410. this.cfg.after.call(this, this.index, this.slides[this.index]);
  411. this.playing && this.play();
  412. }
  413. }
  414. }
  415. each(['Width','Height'],function(i,type){
  416. var _type=type.toLowerCase();
  417. each(['margin','padding','border'],function(j,name){
  418. TouchSlider.fn[name+type]=function(elem){
  419. return parseFloat(this.css(elem,name+'-'+sg[i][1]+(name=='border'?'-width':'')))+parseFloat(this.css(elem,name+'-'+sg[i][2]+(name=='border'?'-width':'')));
  420. }
  421. });
  422. TouchSlider.fn['get'+type]=function(elem){
  423. return elem['offset'+type]-this['padding'+type](elem)-this['border'+type](elem);
  424. }
  425. TouchSlider.fn['getOuter'+type]=function(elem){
  426. return elem['offset'+type]+this['margin'+type](elem);
  427. }
  428. });
  429. window.TouchSlider=TouchSlider;
  430. })(window);