Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
10582 lgm 1
/*
2
 * Swipe 2.0
3
 *
4
 * Brad Birdsall
5
 * Copyright 2013, MIT License
6
 *
7
*/
8
 
9
function Swipe(container, options) {
10
 
11
  "use strict";
12
 
13
  // utilities
14
  var noop = function() {}; // simple no operation function
15
  var offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution
16
 
17
  // check browser capabilities
18
  var browser = {
19
    addEventListener: !!window.addEventListener,
20
    touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
21
    transitions: (function(temp) {
22
      var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
23
      for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
24
      return false;
25
    })(document.createElement('swipe'))
26
  };
27
 
28
  // quit if no root element
29
  if (!container) return;
30
  var element = container.children[0];
31
  var slides, slidePos, width, length;
32
  options = options || {};
33
  var index = parseInt(options.startSlide, 10) || 0;
34
  var speed = options.speed || 300;
35
  options.continuous = options.continuous !== undefined ? options.continuous : true;
36
 
37
  function setup() {
38
 
39
    // cache slides
40
    slides = element.children;
41
    length = slides.length;
42
 
43
    // set continuous to false if only one slide
44
    if (slides.length < 2) options.continuous = false;
45
 
46
    //special case if two slides
47
    if (browser.transitions && options.continuous && slides.length < 3) {
48
      element.appendChild(slides[0].cloneNode(true));
49
      element.appendChild(element.children[1].cloneNode(true));
50
      slides = element.children;
51
    }
52
 
53
    // create an array to store current positions of each slide
54
    slidePos = new Array(slides.length);
55
 
56
    // determine width of each slide
57
    width = container.getBoundingClientRect().width || container.offsetWidth;
58
 
59
    element.style.width = (slides.length * width) + 'px';
60
 
61
    // stack elements
62
    var pos = slides.length;
63
    while(pos--) {
64
 
65
      var slide = slides[pos];
66
 
67
      slide.style.width = width + 'px';
68
      slide.setAttribute('data-index', pos);
69
 
70
      if (browser.transitions) {
71
        slide.style.left = (pos * -width) + 'px';
72
        move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
73
      }
74
 
75
    }
76
 
77
    // reposition elements before and after index
78
    if (options.continuous && browser.transitions) {
79
      move(circle(index-1), -width, 0);
80
      move(circle(index+1), width, 0);
81
    }
82
 
83
    if (!browser.transitions) element.style.left = (index * -width) + 'px';
84
 
85
    container.style.visibility = 'visible';
86
 
87
  }
88
 
89
  function prev() {
90
 
91
    if (options.continuous) slide(index-1);
92
    else if (index) slide(index-1);
93
 
94
  }
95
 
96
  function next() {
97
 
98
    if (options.continuous) slide(index+1);
99
    else if (index < slides.length - 1) slide(index+1);
100
 
101
  }
102
 
103
  function circle(index) {
104
 
105
    // a simple positive modulo using slides.length
106
    return (slides.length + (index % slides.length)) % slides.length;
107
 
108
  }
109
 
110
  function slide(to, slideSpeed) {
111
 
112
    // do nothing if already on requested slide
113
    if (index == to) return;
114
 
115
    if (browser.transitions) {
116
 
117
      var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward
118
 
119
      // get the actual position of the slide
120
      if (options.continuous) {
121
        var natural_direction = direction;
122
        direction = -slidePos[circle(to)] / width;
123
 
124
        // if going forward but to < index, use to = slides.length + to
125
        // if going backward but to > index, use to = -slides.length + to
126
        if (direction !== natural_direction) to =  -direction * slides.length + to;
127
 
128
      }
129
 
130
      var diff = Math.abs(index-to) - 1;
131
 
132
      // move all the slides between index and to in the right direction
133
      while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
134
 
135
      to = circle(to);
136
 
137
      move(index, width * direction, slideSpeed || speed);
138
      move(to, 0, slideSpeed || speed);
139
 
140
      if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
141
 
142
    } else {     
143
 
144
      to = circle(to);
145
      animate(index * -width, to * -width, slideSpeed || speed);
146
      //no fallback for a circular continuous if the browser does not accept transitions
147
    }
148
 
149
    index = to;
150
    offloadFn(options.callback && options.callback(index, slides[index]));
151
  }
152
 
153
  function move(index, dist, speed) {
154
 
155
    translate(index, dist, speed);
156
    slidePos[index] = dist;
157
 
158
  }
159
 
160
  function translate(index, dist, speed) {
161
 
162
    var slide = slides[index];
163
    var style = slide && slide.style;
164
 
165
    if (!style) return;
166
 
167
    style.webkitTransitionDuration = 
168
    style.MozTransitionDuration = 
169
    style.msTransitionDuration = 
170
    style.OTransitionDuration = 
171
    style.transitionDuration = speed + 'ms';
172
 
173
    style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
174
    style.msTransform = 
175
    style.MozTransform = 
176
    style.OTransform = 'translateX(' + dist + 'px)';
177
 
178
  }
179
 
180
  function animate(from, to, speed) {
181
 
182
    // if not an animation, just reposition
183
    if (!speed) {
184
 
185
      element.style.left = to + 'px';
186
      return;
187
 
188
    }
189
 
190
    var start = +new Date;
191
 
192
    var timer = setInterval(function() {
193
 
194
      var timeElap = +new Date - start;
195
 
196
      if (timeElap > speed) {
197
 
198
        element.style.left = to + 'px';
199
 
200
        if (delay) begin();
201
 
202
        options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
203
 
204
        clearInterval(timer);
205
        return;
206
 
207
      }
208
 
209
      element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
210
 
211
    }, 4);
212
 
213
  }
214
 
215
  // setup auto slideshow
216
  var delay = options.auto || 0;
217
  var interval;
218
 
219
  function begin() {
220
 
221
    interval = setTimeout(next, delay);
222
 
223
  }
224
 
225
  function stop() {
226
 
227
    delay = 0;
228
    clearTimeout(interval);
229
 
230
  }
231
 
232
 
233
  // setup initial vars
234
  var start = {};
235
  var delta = {};
236
  var isScrolling;      
237
 
238
  // setup event capturing
239
  var events = {
240
 
241
    handleEvent: function(event) {
242
 
243
      switch (event.type) {
244
        case 'touchstart': this.start(event); break;
245
        case 'touchmove': this.move(event); break;
246
        case 'touchend': offloadFn(this.end(event)); break;
247
        case 'webkitTransitionEnd':
248
        case 'msTransitionEnd':
249
        case 'oTransitionEnd':
250
        case 'otransitionend':
251
        case 'transitionend': offloadFn(this.transitionEnd(event)); break;
252
        case 'resize': offloadFn(setup.call()); break;
253
      }
254
 
255
      if (options.stopPropagation) event.stopPropagation();
256
 
257
    },
258
    start: function(event) {
259
 
260
      var touches = event.touches[0];
261
 
262
      // measure start values
263
      start = {
264
 
265
        // get initial touch coords
266
        x: touches.pageX,
267
        y: touches.pageY,
268
 
269
        // store time to determine touch duration
270
        time: +new Date
271
 
272
      };
273
 
274
      // used for testing first move event
275
      isScrolling = undefined;
276
 
277
      // reset delta and end measurements
278
      delta = {};
279
 
280
      // attach touchmove and touchend listeners
281
      element.addEventListener('touchmove', this, false);
282
      element.addEventListener('touchend', this, false);
283
 
284
    },
285
    move: function(event) {
286
 
287
      // ensure swiping with one touch and not pinching
288
      if ( event.touches.length > 1 || event.scale && event.scale !== 1) return
289
 
290
      if (options.disableScroll) event.preventDefault();
291
 
292
      var touches = event.touches[0];
293
 
294
      // measure change in x and y
295
      delta = {
296
        x: touches.pageX - start.x,
297
        y: touches.pageY - start.y
298
      }
299
 
300
      // determine if scrolling test has run - one time test
301
      if ( typeof isScrolling == 'undefined') {
302
        isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
303
      }
304
 
305
      // if user is not trying to scroll vertically
306
      if (!isScrolling) {
307
 
308
        // prevent native scrolling 
309
        event.preventDefault();
310
 
311
        // stop slideshow
312
        stop();
313
 
314
        // increase resistance if first or last slide
315
        if (options.continuous) { // we don't add resistance at the end
316
 
317
          translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
318
          translate(index, delta.x + slidePos[index], 0);
319
          translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);
320
 
321
        } else {
322
 
323
          delta.x = 
324
            delta.x / 
325
              ( (!index && delta.x > 0               // if first slide and sliding left
326
                || index == slides.length - 1        // or if last slide and sliding right
327
                && delta.x < 0                       // and if sliding at all
328
              ) ?                      
329
              ( Math.abs(delta.x) / width + 1 )      // determine resistance level
330
              : 1 );                                 // no resistance if false
331
 
332
          // translate 1:1
333
          translate(index-1, delta.x + slidePos[index-1], 0);
334
          translate(index, delta.x + slidePos[index], 0);
335
          translate(index+1, delta.x + slidePos[index+1], 0);
336
        }
337
 
338
      }
339
 
340
    },
341
    end: function(event) {
342
 
343
      // measure duration
344
      var duration = +new Date - start.time;
345
 
346
      // determine if slide attempt triggers next/prev slide
347
      var isValidSlide = 
348
            Number(duration) < 250               // if slide duration is less than 250ms
349
            && Math.abs(delta.x) > 20            // and if slide amt is greater than 20px
350
            || Math.abs(delta.x) > width/2;      // or if slide amt is greater than half the width
351
 
352
      // determine if slide attempt is past start and end
353
      var isPastBounds = 
354
            !index && delta.x > 0                            // if first slide and slide amt is greater than 0
355
            || index == slides.length - 1 && delta.x < 0;    // or if last slide and slide amt is less than 0
356
 
357
      if (options.continuous) isPastBounds = false;
358
 
359
      // determine direction of swipe (true:right, false:left)
360
      var direction = delta.x < 0;
361
 
362
      // if not scrolling vertically
363
      if (!isScrolling) {
364
 
365
        if (isValidSlide && !isPastBounds) {
366
 
367
          if (direction) {
368
 
369
            if (options.continuous) { // we need to get the next in this direction in place
370
 
371
              move(circle(index-1), -width, 0);
372
              move(circle(index+2), width, 0);
373
 
374
            } else {
375
              move(index-1, -width, 0);
376
            }
377
 
378
            move(index, slidePos[index]-width, speed);
379
            move(circle(index+1), slidePos[circle(index+1)]-width, speed);
380
            index = circle(index+1);  
381
 
382
          } else {
383
            if (options.continuous) { // we need to get the next in this direction in place
384
 
385
              move(circle(index+1), width, 0);
386
              move(circle(index-2), -width, 0);
387
 
388
            } else {
389
              move(index+1, width, 0);
390
            }
391
 
392
            move(index, slidePos[index]+width, speed);
393
            move(circle(index-1), slidePos[circle(index-1)]+width, speed);
394
            index = circle(index-1);
395
 
396
          }
397
 
398
          options.callback && options.callback(index, slides[index]);
399
 
400
        } else {
401
 
402
          if (options.continuous) {
403
 
404
            move(circle(index-1), -width, speed);
405
            move(index, 0, speed);
406
            move(circle(index+1), width, speed);
407
 
408
          } else {
409
 
410
            move(index-1, -width, speed);
411
            move(index, 0, speed);
412
            move(index+1, width, speed);
413
          }
414
 
415
        }
416
 
417
      }
418
 
419
      // kill touchmove and touchend event listeners until touchstart called again
420
      element.removeEventListener('touchmove', events, false)
421
      element.removeEventListener('touchend', events, false)
422
 
423
    },
424
    transitionEnd: function(event) {
425
 
426
      if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
427
 
428
        if (delay) begin();
429
 
430
        options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
431
 
432
      }
433
 
434
    }
435
 
436
  }
437
 
438
  // trigger setup
439
  setup();
440
 
441
  // start auto slideshow if applicable
442
  if (delay) begin();
443
 
444
 
445
  // add event listeners
446
  if (browser.addEventListener) {
447
 
448
    // set touchstart event on element    
449
    if (browser.touch) element.addEventListener('touchstart', events, false);
450
 
451
    if (browser.transitions) {
452
      element.addEventListener('webkitTransitionEnd', events, false);
453
      element.addEventListener('msTransitionEnd', events, false);
454
      element.addEventListener('oTransitionEnd', events, false);
455
      element.addEventListener('otransitionend', events, false);
456
      element.addEventListener('transitionend', events, false);
457
    }
458
 
459
    // set resize event on window
460
    window.addEventListener('resize', events, false);
461
 
462
  } else {
463
 
464
    window.onresize = function () { setup() }; // to play nice with old IE
465
 
466
  }
467
 
468
  // expose the Swipe API
469
  return {
470
    setup: function() {
471
 
472
      setup();
473
 
474
    },
475
    slide: function(to, speed) {
476
 
477
      // cancel slideshow
478
      stop();
479
 
480
      slide(to, speed);
481
 
482
    },
483
    prev: function() {
484
 
485
      // cancel slideshow
486
      stop();
487
 
488
      prev();
489
 
490
    },
491
    next: function() {
492
 
493
      // cancel slideshow
494
      stop();
495
 
496
      next();
497
 
498
    },
499
    getPos: function() {
500
 
501
      // return current index position
502
      return index;
503
 
504
    },
505
    getNumSlides: function() {
506
 
507
      // return total number of slides
508
      return length;
509
    },
510
    kill: function() {
511
 
512
      // cancel slideshow
513
      stop();
514
 
515
      // reset element
516
      element.style.width = 'auto';
517
      element.style.left = 0;
518
 
519
      // reset slides
520
      var pos = slides.length;
521
      while(pos--) {
522
 
523
        var slide = slides[pos];
524
        slide.style.width = '100%';
525
        slide.style.left = 0;
526
 
527
        if (browser.transitions) translate(pos, 0, 0);
528
 
529
      }
530
 
531
      // removed event listeners
532
      if (browser.addEventListener) {
533
 
534
        // remove current event listeners
535
        element.removeEventListener('touchstart', events, false);
536
        element.removeEventListener('webkitTransitionEnd', events, false);
537
        element.removeEventListener('msTransitionEnd', events, false);
538
        element.removeEventListener('oTransitionEnd', events, false);
539
        element.removeEventListener('otransitionend', events, false);
540
        element.removeEventListener('transitionend', events, false);
541
        window.removeEventListener('resize', events, false);
542
 
543
      }
544
      else {
545
 
546
        window.onresize = null;
547
 
548
      }
549
 
550
    }
551
  }
552
 
553
}
554
 
555
 
556
if ( window.jQuery || window.Zepto ) {
557
  (function($) {
558
    $.fn.Swipe = function(params) {
559
      return this.each(function() {
560
        $(this).data('Swipe', new Swipe($(this)[0], params));
561
      });
562
    }
563
  })( window.jQuery || window.Zepto )
564
}