/* * cloud 9 carousel 2.0 * cleaned up, refactored, and improved version of cloudcarousel * * see the demo and get the latest version on github: * http://specious.github.io/cloud9carousel/ * * copyright (c) 2014 by ildar sagdejev ( http://twitter.com/tknomad ) * 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 later -or- zepto 1.1.1 or later * * 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 { // the gap between the image and its reflection doesn't resize automatically 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 = (function() { return !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.onloaded = options.onloaded; this.onrendered = options.onrendered; this.itemoptions = { transforms: options.transforms } if( options.mirror ) { this.itemoptions.mirror = $.extend( { gap: 2 }, options.mirror ); } $container.css( { position: 'relative', overflow: 'inherit' } ); // rotation: // * 0 : right // * pi/2 : front // * pi : left // * 3 pi/2 : back this.rotateitem = 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 ); } this.render = function() { var count = this.items.length; var spacing = 2 * math.pi / count; var radians = this.rotation; for( var i = 0; i < count; i++ ) { this.rotateitem( i, radians ); 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(); } else { // rotate asymptotically closer to 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. count is the number (+-) of carousel items to rotate // this.go = function( count ) { this.destrotation += (2 * math.pi / this.items.length) * count; this.play(); } this.deactivate = function() { this.pause(); clearinterval( this.autoplaytimer ); options.buttonleft.unbind( 'click' ); 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() { options.buttonleft.bind( 'click', function() { self.go( -1 ); return false; } ); 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 idx = self.items.indexof( hits[0].item ); var count = self.items.length; var diff = idx - (self.floatindex() % count); // choose direction based on which way is shortest if( 2 * math.abs(diff) > count ) diff += (diff > 0) ? -count : count; self.destrotation = self.rotation; self.go( -diff ); } } ); } } 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', handle: 'carousel' }, options ); $(this).data( options.handle, new carousel( this, options ) ); } ); } })( window.jquery || window.zepto );