/* * 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(); } /* 이미지클릭시 오토플레이 리셋 */ clearInterval( self.autoPlayTimer ); self.autoPlay(); }); } } 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 );