123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- /*
- * Cloud 9 Carousel 2.2.0
- *
- * Pseudo-3D carousel plugin for jQuery/Zepto focused on performance.
- *
- * Based on the original CloudCarousel by R. Cecco.
- *
- * See the demo and download the latest version:
- * http://specious.github.io/cloud9carousel/
- *
- * Copyright (c) 2017 by Ildar Sagdejev ( http://specious.github.io )
- * Copyright (c) 2011 by R. Cecco ( http://www.professorcloud.com )
- *
- * MIT License
- *
- * Please retain this copyright header in all versions of the software
- *
- * Requires:
- * - jQuery >= 1.3.0 or Zepto >= 1.1.1
- *
- * Optional (jQuery only):
- * - Reflection support via reflection.js plugin by Christophe Beyls
- * http://www.digitalia.be/software/reflectionjs-for-jquery
- * - Mousewheel support via mousewheel plugin
- * http://plugins.jquery.com/mousewheel/
- */
- ;(function($) {
- //
- // Detect CSS transform support
- //
- var transform = (function() {
- var vendors = ['webkit', 'moz', 'ms'];
- var style = document.createElement( "div" ).style;
- var trans = 'transform' in style ? 'transform' : undefined;
- for( var i = 0, count = vendors.length; i < count; i++ ) {
- var prop = vendors[i] + 'Transform';
- if( prop in style ) {
- trans = prop;
- break;
- }
- }
- return trans;
- })();
- var Item = function( element, options ) {
- element.item = this;
- this.element = element;
- if( element.tagName === 'IMG' ) {
- this.fullWidth = element.width;
- this.fullHeight = element.height;
- } else {
- element.style.display = "inline-block";
- this.fullWidth = element.offsetWidth;
- this.fullHeight = element.offsetHeight;
- }
- element.style.position = 'absolute';
- if( options.mirror && this.element.tagName === 'IMG' ) {
- // Wrap image in a div together with its generated reflection
- this.reflection = $(element).reflect( options.mirror ).next()[0];
- var $reflection = $(this.reflection);
- this.reflection.fullHeight = $reflection.height();
- $reflection.css( 'margin-top', options.mirror.gap + 'px' );
- $reflection.css( 'width', '100%' );
- element.style.width = "100%";
- // The item element now contains the image and reflection
- this.element = this.element.parentNode;
- this.element.item = this;
- this.element.alt = element.alt;
- this.element.title = element.title;
- }
- if( transform && options.transforms )
- this.element.style[transform + "Origin"] = "0 0";
- this.moveTo = function( x, y, scale ) {
- this.width = this.fullWidth * scale;
- this.height = this.fullHeight * scale;
- this.x = x;
- this.y = y;
- this.scale = scale;
- var style = this.element.style;
- style.zIndex = "" + (scale * 100) | 0;
- if( transform && options.transforms ) {
- style[transform] = "translate(" + x + "px, " + y + "px) scale(" + scale + ")";
- } else {
- // Manually resize the gap between the image and its reflection
- if( options.mirror && this.element.tagName === 'IMG' )
- this.reflection.style.marginTop = (options.mirror.gap * scale) + "px";
- style.width = this.width + "px";
- style.left = x + "px";
- style.top = y + "px";
- }
- }
- }
- var time = !window.performance || !window.performance.now ?
- function() { return +new Date() } :
- function() { return performance.now() };
- //
- // Detect requestAnimationFrame() support
- //
- // Support legacy browsers:
- // http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
- //
- var cancelFrame = window.cancelAnimationFrame || window.cancelRequestAnimationFrame;
- var requestFrame = window.requestAnimationFrame;
- (function() {
- var vendors = ['webkit', 'moz', 'ms'];
- for( var i = 0, count = vendors.length; i < count && !cancelFrame; i++ ) {
- cancelFrame = window[vendors[i]+'CancelAnimationFrame'] || window[vendors[i]+'CancelRequestAnimationFrame'];
- requestFrame = requestFrame && window[vendors[i]+'RequestAnimationFrame'];
- }
- }());
- var Carousel = function( element, options ) {
- var self = this;
- var $container = $(element);
- this.items = [];
- this.xOrigin = (options.xOrigin === null) ? $container.width() * 0.5 : options.xOrigin;
- this.yOrigin = (options.yOrigin === null) ? $container.height() * 0.1 : options.yOrigin;
- this.xRadius = (options.xRadius === null) ? $container.width() / 2.3 : options.xRadius;
- this.yRadius = (options.yRadius === null) ? $container.height() / 6 : options.yRadius;
- this.farScale = options.farScale;
- this.rotation = this.destRotation = Math.PI/2; // start with the first item positioned in front
- this.speed = options.speed;
- this.smooth = options.smooth;
- this.fps = options.fps;
- this.timer = 0;
- this.autoPlayAmount = options.autoPlay;
- this.autoPlayDelay = options.autoPlayDelay;
- this.autoPlayTimer = 0;
- this.frontItemClass = options.frontItemClass;
- this.onLoaded = options.onLoaded;
- this.onRendered = options.onRendered;
- this.onAnimationFinished = options.onAnimationFinished;
- this.itemOptions = {
- transforms: options.transforms
- }
- if( options.mirror ) {
- this.itemOptions.mirror = $.extend( { gap: 2 }, options.mirror );
- }
- $container.css( { position: 'relative', overflow: 'hidden' } );
- // Rotation:
- // * 0 : right
- // * Pi/2 : front
- // * Pi : left
- // * 3 Pi/2 : back
- this.renderItem = function( itemIndex, rotation ) {
- var item = this.items[itemIndex];
- var sin = Math.sin(rotation);
- var farScale = this.farScale;
- var scale = farScale + ((1-farScale) * (sin+1) * 0.5);
- item.moveTo(
- this.xOrigin + (scale * ((Math.cos(rotation) * this.xRadius) - (item.fullWidth * 0.5))),
- this.yOrigin + (scale * sin * this.yRadius),
- scale
- );
- return item;
- }
- this.render = function() {
- var count = this.items.length;
- var spacing = 2 * Math.PI / count;
- var radians = this.rotation;
- var nearest = this.nearestIndex();
- for( var i = 0; i < count; i++ ) {
- var item = this.renderItem( i, radians );
- if( i === nearest )
- $(item.element).addClass( this.frontItemClass );
- else
- $(item.element).removeClass( this.frontItemClass );
- radians += spacing;
- }
- if( typeof this.onRendered === 'function' )
- this.onRendered( this );
- }
- this.playFrame = function() {
- var rem = self.destRotation - self.rotation;
- var now = time();
- var dt = (now - self.lastTime) * 0.002;
- self.lastTime = now;
- if( Math.abs(rem) < 0.003 ) {
- self.rotation = self.destRotation;
- self.pause();
- if( typeof self.onAnimationFinished === 'function' )
- self.onAnimationFinished();
- } else {
- // Asymptotically approach the destination
- self.rotation = self.destRotation - rem / (1 + (self.speed * dt));
- self.scheduleNextFrame();
- }
- self.render();
- }
- this.scheduleNextFrame = function() {
- this.lastTime = time();
- this.timer = this.smooth && cancelFrame ?
- requestFrame( self.playFrame ) :
- setTimeout( self.playFrame, 1000 / this.fps );
- }
- this.itemsRotated = function() {
- return this.items.length * ((Math.PI/2) - this.rotation) / (2*Math.PI);
- }
- this.floatIndex = function() {
- var count = this.items.length;
- var floatIndex = this.itemsRotated() % count;
- // Make sure float-index is positive
- return (floatIndex < 0) ? floatIndex + count : floatIndex;
- }
- this.nearestIndex = function() {
- return Math.round( this.floatIndex() ) % this.items.length;
- }
- this.nearestItem = function() {
- return this.items[this.nearestIndex()];
- }
- this.play = function() {
- if( this.timer === 0 )
- this.scheduleNextFrame();
- }
- this.pause = function() {
- this.smooth && cancelFrame ? cancelFrame( this.timer ) : clearTimeout( this.timer );
- this.timer = 0;
- }
- //
- // Spin the carousel by (+-) count items
- //
- this.go = function( count ) {
- this.destRotation += (2 * Math.PI / this.items.length) * count;
- this.play();
- }
- this.goTo = function( index ) {
- var count = this.items.length;
- // Find the shortest way to rotate item to front
- var diff = index - (this.floatIndex() % count);
- if( 2 * Math.abs(diff) > count )
- diff -= (diff > 0) ? count : -count;
- // Halt any rotation already in progress
- this.destRotation = this.rotation;
- // Spin the opposite way to bring item to front
- this.go( -diff );
- // Return rotational distance (in items) to the target
- return diff;
- }
- this.deactivate = function() {
- this.pause();
- clearInterval( this.autoPlayTimer );
- if( options.buttonLeft ) options.buttonLeft.unbind( 'click' );
- if( options.buttonRight ) options.buttonRight.unbind( 'click' );
- $container.unbind( '.cloud9' );
- }
- this.autoPlay = function() {
- this.autoPlayTimer = setInterval(
- function() { self.go( self.autoPlayAmount ) },
- this.autoPlayDelay
- );
- }
- this.enableAutoPlay = function() {
- // Stop auto-play on mouse over
- $container.bind( 'mouseover.cloud9', function() {
- clearInterval( self.autoPlayTimer );
- } );
- // Resume auto-play when mouse leaves the container
- $container.bind( 'mouseout.cloud9', function() {
- self.autoPlay();
- } );
- this.autoPlay();
- }
- this.bindControls = function() {
- if( options.buttonLeft ) {
- options.buttonLeft.bind( 'click', function() {
- self.go( -1 );
- return false;
- } );
- }
- if( options.buttonRight ) {
- options.buttonRight.bind( 'click', function() {
- self.go( 1 );
- return false;
- } );
- }
- if( options.mouseWheel ) {
- $container.bind( 'mousewheel.cloud9', function( event, delta ) {
- self.go( (delta > 0) ? 1 : -1 );
- return false;
- } );
- }
- if( options.bringToFront ) {
- $container.bind( 'click.cloud9', function( event ) {
- var hits = $(event.target).closest( '.' + options.itemClass );
- if( hits.length !== 0 ) {
- var diff = self.goTo( self.items.indexOf( hits[0].item ) );
- // Suppress default browser action if the item isn't roughly in front
- if( Math.abs(diff) > 0.5 )
- event.preventDefault();
- }
- } );
- }
- }
- var items = $container.find( '.' + options.itemClass );
- this.finishInit = function() {
- //
- // Wait until all images have completely loaded
- //
- for( var i = 0; i < items.length; i++ ) {
- var item = items[i];
- if( (item.tagName === 'IMG') &&
- ((item.width === undefined) || ((item.complete !== undefined) && !item.complete)) )
- return;
- }
- clearInterval( this.initTimer );
- // Init items
- for( i = 0; i < items.length; i++ )
- this.items.push( new Item( items[i], this.itemOptions ) );
- // Disable click-dragging of items
- $container.bind( 'mousedown onselectstart', function() { return false } );
- if( this.autoPlayAmount !== 0 ) this.enableAutoPlay();
- this.bindControls();
- this.render();
- if( typeof this.onLoaded === 'function' )
- this.onLoaded( this );
- };
- this.initTimer = setInterval( function() { self.finishInit() }, 50 );
- }
- //
- // The jQuery plugin
- //
- $.fn.Cloud9Carousel = function( options ) {
- return this.each( function() {
- /* For full list of options see the README */
- options = $.extend( {
- xOrigin: null, // null: calculated automatically
- yOrigin: null,
- xRadius: null,
- yRadius: null,
- farScale: 0.5, // scale of the farthest item
- transforms: true, // enable CSS transforms
- smooth: true, // enable smooth animation via requestAnimationFrame()
- fps: 30, // fixed frames per second (if smooth animation is off)
- speed: 4, // positive number
- autoPlay: 0, // [ 0: off | number of items (integer recommended, positive is clockwise) ]
- autoPlayDelay: 4000,
- bringToFront: false,
- itemClass: 'cloud9-item',
- frontItemClass: null,
- handle: 'carousel'
- }, options );
- $(this).data( options.handle, new Carousel( this, options ) );
- } );
- }
- })( window.jQuery || window.Zepto );
|