Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
27171 tejbeer 1
/*
2
 * Cloud 9 Carousel 2.2.0
3
 *
4
 * Pseudo-3D carousel plugin for jQuery/Zepto focused on performance.
5
 *
6
 * Based on the original CloudCarousel by R. Cecco.
7
 *
8
 * See the demo and download the latest version:
9
 *   http://specious.github.io/cloud9carousel/
10
 *
11
 * Copyright (c) 2017 by Ildar Sagdejev ( http://specious.github.io )
12
 * Copyright (c) 2011 by R. Cecco ( http://www.professorcloud.com )
13
 *
14
 * MIT License
15
 *
16
 * Please retain this copyright header in all versions of the software
17
 *
18
 * Requires:
19
 *  - jQuery >= 1.3.0 or Zepto >= 1.1.1
20
 *
21
 * Optional (jQuery only):
22
 *  - Reflection support via reflection.js plugin by Christophe Beyls
23
 *     http://www.digitalia.be/software/reflectionjs-for-jquery
24
 *  - Mousewheel support via mousewheel plugin
25
 *     http://plugins.jquery.com/mousewheel/
26
 */
27
 
28
;(function($) {
29
  //
30
  // Detect CSS transform support
31
  //
32
  var transform = (function() {
33
    var vendors = ['webkit', 'moz', 'ms'];
34
    var style   = document.createElement( "div" ).style;
35
    var trans   = 'transform' in style ? 'transform' : undefined;
36
 
37
    for( var i = 0, count = vendors.length; i < count; i++ ) {
38
      var prop = vendors[i] + 'Transform';
39
      if( prop in style ) {
40
        trans = prop;
41
        break;
42
      }
43
    }
44
 
45
    return trans;
46
  })();
47
 
48
  var Item = function( element, options ) {
49
    element.item = this;
50
    this.element = element;
51
 
52
    if( element.tagName === 'IMG' ) {
53
      this.fullWidth = element.width;
54
      this.fullHeight = element.height;
55
    } else {
56
      element.style.display = "inline-block";
57
      this.fullWidth = element.offsetWidth;
58
      this.fullHeight = element.offsetHeight;
59
    }
60
 
61
    element.style.position = 'absolute';
62
 
63
    if( options.mirror && this.element.tagName === 'IMG' ) {
64
      // Wrap image in a div together with its generated reflection
65
      this.reflection = $(element).reflect( options.mirror ).next()[0];
66
 
67
      var $reflection = $(this.reflection);
68
      this.reflection.fullHeight = $reflection.height();
69
      $reflection.css( 'margin-top', options.mirror.gap + 'px' );
70
      $reflection.css( 'width', '100%' );
71
      element.style.width = "100%";
72
 
73
      // The item element now contains the image and reflection
74
      this.element = this.element.parentNode;
75
      this.element.item  = this;
76
      this.element.alt   = element.alt;
77
      this.element.title = element.title;
78
    }
79
 
80
    if( transform && options.transforms )
81
      this.element.style[transform + "Origin"] = "0 0";
82
 
83
    this.moveTo = function( x, y, scale ) {
84
      this.width = this.fullWidth * scale;
85
      this.height = this.fullHeight * scale;
86
      this.x = x;
87
      this.y = y;
88
      this.scale = scale;
89
 
90
      var style = this.element.style;
91
      style.zIndex = "" + (scale * 100) | 0;
92
 
93
      if( transform && options.transforms ) {
94
        style[transform] = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
95
      } else {
96
        // Manually resize the gap between the image and its reflection
97
        if( options.mirror && this.element.tagName === 'IMG' )
98
          this.reflection.style.marginTop = (options.mirror.gap * scale) + "px";
99
 
100
        style.width = this.width + "px";
101
        style.left = x + "px";
102
        style.top = y + "px";
103
      }
104
    }
105
  }
106
 
107
  var time = !window.performance || !window.performance.now ?
108
    function() { return +new Date() } :
109
    function() { return performance.now() };
110
 
111
  //
112
  // Detect requestAnimationFrame() support
113
  //
114
  // Support legacy browsers:
115
  //   http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
116
  //
117
  var cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame;
118
  var requestFrame = window.requestAnimationFrame;
119
 
120
  (function() {
121
    var vendors = ['webkit', 'moz', 'ms'];
122
 
123
    for( var i = 0, count = vendors.length; i < count && !cancelFrame; i++ ) {
124
      cancelFrame = window[vendors[i]+'CancelAnimationFrame'] || window[vendors[i]+'CancelRequestAnimationFrame'];
125
      requestFrame = requestFrame && window[vendors[i]+'RequestAnimationFrame'];
126
    }
127
  }());
128
 
129
  var Carousel = function( element, options ) {
130
    var self = this;
131
    var $container = $(element);
132
    this.items = [];
133
    this.xOrigin = (options.xOrigin === null) ? $container.width()  * 0.5 : options.xOrigin;
134
    this.yOrigin = (options.yOrigin === null) ? $container.height() * 0.1 : options.yOrigin;
135
    this.xRadius = (options.xRadius === null) ? $container.width()  / 2.3 : options.xRadius;
136
    this.yRadius = (options.yRadius === null) ? $container.height() / 6   : options.yRadius;
137
    this.farScale = options.farScale;
138
    this.rotation = this.destRotation = Math.PI/2; // start with the first item positioned in front
139
    this.speed = options.speed;
140
    this.smooth = options.smooth;
141
    this.fps = options.fps;
142
    this.timer = 0;
143
    this.autoPlayAmount = options.autoPlay;
144
    this.autoPlayDelay = options.autoPlayDelay;
145
    this.autoPlayTimer = 0;
146
    this.frontItemClass = options.frontItemClass;
147
    this.onLoaded = options.onLoaded;
148
    this.onRendered = options.onRendered;
149
    this.onAnimationFinished = options.onAnimationFinished;
150
 
151
    this.itemOptions = {
152
      transforms: options.transforms
153
    }
154
 
155
    if( options.mirror ) {
156
      this.itemOptions.mirror = $.extend( { gap: 2 }, options.mirror );
157
    }
158
 
159
    $container.css( { position: 'relative', overflow: 'hidden' } );
160
 
161
    // Rotation:
162
    //  *      0 : right
163
    //  *   Pi/2 : front
164
    //  *   Pi   : left
165
    //  * 3 Pi/2 : back
166
    this.renderItem = function( itemIndex, rotation ) {
167
      var item = this.items[itemIndex];
168
      var sin = Math.sin(rotation);
169
      var farScale = this.farScale;
170
      var scale = farScale + ((1-farScale) * (sin+1) * 0.5);
171
 
172
      item.moveTo(
173
        this.xOrigin + (scale * ((Math.cos(rotation) * this.xRadius) - (item.fullWidth * 0.5))),
174
        this.yOrigin + (scale * sin * this.yRadius),
175
        scale
176
      );
177
 
178
      return item;
179
    }
180
 
181
    this.render = function() {
182
      var count = this.items.length;
183
      var spacing = 2 * Math.PI / count;
184
      var radians = this.rotation;
185
      var nearest = this.nearestIndex();
186
 
187
      for( var i = 0; i < count; i++ ) {
188
        var item = this.renderItem( i, radians );
189
 
190
        if( i === nearest )
191
          $(item.element).addClass( this.frontItemClass );
192
        else
193
          $(item.element).removeClass( this.frontItemClass );
194
 
195
        radians += spacing;
196
      }
197
 
198
      if( typeof this.onRendered === 'function' )
199
        this.onRendered( this );
200
    }
201
 
202
    this.playFrame = function() {
203
      var rem = self.destRotation - self.rotation;
204
      var now = time();
205
      var dt = (now - self.lastTime) * 0.002;
206
      self.lastTime = now;
207
 
208
      if( Math.abs(rem) < 0.003 ) {
209
        self.rotation = self.destRotation;
210
        self.pause();
211
 
212
        if( typeof self.onAnimationFinished === 'function' )
213
          self.onAnimationFinished();
214
      } else {
215
        // Asymptotically approach the destination
216
        self.rotation = self.destRotation - rem / (1 + (self.speed * dt));
217
        self.scheduleNextFrame();
218
      }
219
 
220
      self.render();
221
    }
222
 
223
    this.scheduleNextFrame = function() {
224
      this.lastTime = time();
225
 
226
      this.timer = this.smooth && cancelFrame ?
227
        requestFrame( self.playFrame ) :
228
        setTimeout( self.playFrame, 1000 / this.fps );
229
    }
230
 
231
    this.itemsRotated = function() {
232
      return this.items.length * ((Math.PI/2) - this.rotation) / (2*Math.PI);
233
    }
234
 
235
    this.floatIndex = function() {
236
      var count = this.items.length;
237
      var floatIndex = this.itemsRotated() % count;
238
 
239
      // Make sure float-index is positive
240
      return (floatIndex < 0) ? floatIndex + count : floatIndex;
241
    }
242
 
243
    this.nearestIndex = function() {
244
      return Math.round( this.floatIndex() ) % this.items.length;
245
    }
246
 
247
    this.nearestItem = function() {
248
      return this.items[this.nearestIndex()];
249
    }
250
 
251
    this.play = function() {
252
      if( this.timer === 0 )
253
        this.scheduleNextFrame();
254
    }
255
 
256
    this.pause = function() {
257
      this.smooth && cancelFrame ? cancelFrame( this.timer ) : clearTimeout( this.timer );
258
      this.timer = 0;
259
    }
260
 
261
    //
262
    // Spin the carousel by (+-) count items
263
    //
264
    this.go = function( count ) {
265
      this.destRotation += (2 * Math.PI / this.items.length) * count;
266
      this.play();
267
    }
268
 
269
    this.goTo = function( index ) {
270
      var count = this.items.length;
271
 
272
      // Find the shortest way to rotate item to front
273
      var diff = index - (this.floatIndex() % count);
274
 
275
      if( 2 * Math.abs(diff) > count )
276
        diff -= (diff > 0) ? count : -count;
277
 
278
      // Halt any rotation already in progress
279
      this.destRotation = this.rotation;
280
 
281
      // Spin the opposite way to bring item to front
282
      this.go( -diff );
283
 
284
      // Return rotational distance (in items) to the target
285
      return diff;
286
    }
287
 
288
    this.deactivate = function() {
289
      this.pause();
290
      clearInterval( this.autoPlayTimer );
291
      if( options.buttonLeft ) options.buttonLeft.unbind( 'click' );
292
      if( options.buttonRight ) options.buttonRight.unbind( 'click' );
293
      $container.unbind( '.cloud9' );
294
    }
295
 
296
    this.autoPlay = function() {
297
      this.autoPlayTimer = setInterval(
298
        function() { self.go( self.autoPlayAmount ) },
299
        this.autoPlayDelay
300
      );
301
    }
302
 
303
    this.enableAutoPlay = function() {
304
      // Stop auto-play on mouse over
305
      $container.bind( 'mouseover.cloud9', function() {
306
        clearInterval( self.autoPlayTimer );
307
      } );
308
 
309
      // Resume auto-play when mouse leaves the container
310
      $container.bind( 'mouseout.cloud9', function() {
311
        self.autoPlay();
312
      } );
313
 
314
      this.autoPlay();
315
    }
316
 
317
    this.bindControls = function() {
318
      if( options.buttonLeft ) {
319
        options.buttonLeft.bind( 'click', function() {
320
          self.go( -1 );
321
          return false;
322
        } );
323
      }
324
 
325
      if( options.buttonRight ) {
326
        options.buttonRight.bind( 'click', function() {
327
          self.go( 1 );
328
          return false;
329
        } );
330
      }
331
 
332
      if( options.mouseWheel ) {
333
        $container.bind( 'mousewheel.cloud9', function( event, delta ) {
334
          self.go( (delta > 0) ? 1 : -1 );
335
          return false;
336
        } );
337
      }
338
 
339
      if( options.bringToFront ) {
340
        $container.bind( 'click.cloud9', function( event ) {
341
          var hits = $(event.target).closest( '.' + options.itemClass );
342
 
343
          if( hits.length !== 0 ) {
344
            var diff = self.goTo( self.items.indexOf( hits[0].item ) );
345
 
346
            // Suppress default browser action if the item isn't roughly in front
347
            if( Math.abs(diff) > 0.5 )
348
              event.preventDefault();
349
          }
350
        } );
351
      }
352
    }
353
 
354
    var items = $container.find( '.' + options.itemClass );
355
 
356
    this.finishInit = function() {
357
      //
358
      // Wait until all images have completely loaded
359
      //
360
      for( var i = 0; i < items.length; i++ ) {
361
        var item = items[i];
362
        if( (item.tagName === 'IMG') &&
363
            ((item.width === undefined) || ((item.complete !== undefined) && !item.complete)) )
364
          return;
365
      }
366
 
367
      clearInterval( this.initTimer );
368
 
369
      // Init items
370
      for( i = 0; i < items.length; i++ )
371
        this.items.push( new Item( items[i], this.itemOptions ) );
372
 
373
      // Disable click-dragging of items
374
      $container.bind( 'mousedown onselectstart', function() { return false } );
375
 
376
      if( this.autoPlayAmount !== 0 ) this.enableAutoPlay();
377
      this.bindControls();
378
      this.render();
379
 
380
      if( typeof this.onLoaded === 'function' )
381
        this.onLoaded( this );
382
    };
383
 
384
    this.initTimer = setInterval( function() { self.finishInit() }, 50 );
385
  }
386
 
387
  //
388
  // The jQuery plugin
389
  //
390
  $.fn.Cloud9Carousel = function( options ) {
391
    return this.each( function() {
392
      /* For full list of options see the README */
393
      options = $.extend( {
394
        xOrigin: null,        // null: calculated automatically
395
        yOrigin: null,
396
        xRadius: null,
397
        yRadius: null,
398
        farScale: 0.5,        // scale of the farthest item
399
        transforms: true,     // enable CSS transforms
400
        smooth: true,         // enable smooth animation via requestAnimationFrame()
401
        fps: 30,              // fixed frames per second (if smooth animation is off)
402
        speed: 4,             // positive number
403
        autoPlay: 0,          // [ 0: off | number of items (integer recommended, positive is clockwise) ]
404
        autoPlayDelay: 4000,
405
        bringToFront: false,
406
        itemClass: 'cloud9-item',
407
        frontItemClass: null,
408
        handle: 'carousel'
409
      }, options );
410
 
411
      $(this).data( options.handle, new Carousel( this, options ) );
412
    } );
413
  }
414
})( window.jQuery || window.Zepto );