; (function ($) { 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') { 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%"; 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 { 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() }; 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.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'}); 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 ); } 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.renderItem(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(); if (typeof self.onAnimationFinished === 'function') self.onAnimationFinished(); } 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; } this.go = function (count) { this.destRotation += (2 * Math.PI / this.items.length) * count; this.play(); } 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 idx = self.items.indexOf(hits[0].item); var count = self.items.length; var diff = idx - (self.floatIndex() % count); if (2 * Math.abs(diff) > count) diff += (diff > 0) ? -count : count; if (Math.abs(diff) > 0.5) event.preventDefault(); self.destRotation = self.rotation; self.go(-diff); } }); } } var items = $container.find('.' + options.itemClass); this.finishInit = function () { 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.CoverCarousel = function (options) { return this.each(function () { 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);