Skip to content

Instantly share code, notes, and snippets.

@noeltock
Created October 9, 2012 13:42
Show Gist options
  • Save noeltock/3858892 to your computer and use it in GitHub Desktop.
Save noeltock/3858892 to your computer and use it in GitHub Desktop.
/**
* jquery.gridrotator.js v1.0.0
* http://www.codrops.com
*
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2012, Codrops
* http://www.codrops.com
*/
;( function( $, window, undefined ) {
'use strict';
// http://www.hardcode.nl/subcategory_1/article_317-array-shuffle-function
Array.prototype.shuffle = function() {
var i=this.length,p,t;
while (i--) {
p = Math.floor(Math.random()*i);
t = this[i];
this[i]=this[p];
this[p]=t;
}
return this;
};
/*
* debouncedresize: special jQuery event that happens once after a window resize
*
* latest version and complete README available on Github:
* https://github.com/louisremi/jquery-smartresize/blob/master/jquery.debouncedresize.js
*
* Copyright 2011 @louis_remi
* Licensed under the MIT license.
*/
var $event = $.event,
$special,
resizeTimeout;
$special = $event.special.debouncedresize = {
setup: function() {
$( this ).on( "resize", $special.handler );
},
teardown: function() {
$( this ).off( "resize", $special.handler );
},
handler: function( event, execAsap ) {
// Save the context
var context = this,
args = arguments,
dispatch = function() {
// set correct event type
event.type = "debouncedresize";
$event.dispatch.apply( context, args );
};
if ( resizeTimeout ) {
clearTimeout( resizeTimeout );
}
execAsap ?
dispatch() :
resizeTimeout = setTimeout( dispatch, $special.threshold );
},
threshold: 50
};
// global
var $window = $( window ),
Modernizr = window.Modernizr;
$.GridRotator = function( options, element ) {
this.$el = $( element );
if( Modernizr.backgroundsize ) {
var _self = this;
this.$el.addClass( 'ri-grid-loading' );
$( '<img/>' ).load( function() {
_self._init( options );
} ).attr( 'src', grid_loading_url );
}
};
$.GridRotator.defaults = {
// number of rows
rows : 4,
// number of columns
columns : 10,
w1024 : {
rows : 3,
columns : 8
},
w768 : {
rows : 3,
columns : 7
},
w480 : {
rows : 3,
columns : 5
},
w320 : {
rows : 2,
columns : 4
},
w240 : {
rows : 2,
columns : 3
},
// step: number of items that are replaced at the same time
// random || [some number]
// note: for performance issues, the number "can't" be > options.maxStep
step : 'random',
maxStep : 3,
// prevent user to click the items
preventClick : true,
// animation type
// showHide || fadeInOut || slideLeft ||
// slideRight || slideTop || slideBottom ||
// rotateLeft || rotateRight || rotateTop ||
// rotateBottom || scale || rotate3d ||
// rotateLeftScale || rotateRightScale ||
// rotateTopScale || rotateBottomScale || random
animType : 'random',
// animation speed
animSpeed : 500,
// animation easings
animEasingOut : 'linear',
animEasingIn : 'linear',
// the item(s) will be replaced every 3 seconds
// note: for performance issues, the time "can't" be < 300 ms
interval : 3000
};
$.GridRotator.prototype = {
_init : function( options ) {
var _self = this;
// options
this.options = $.extend( true, {}, $.GridRotator.defaults, options );
// all types of animations
this.animTypesAll = [ 'fadeInOut', 'slideLeft', 'slideRight', 'slideTop', 'slideBottom', 'rotateLeft', 'rotateRight', 'rotateTop', 'rotateBottom', 'scale', 'rotate3d', 'rotateLeftScale', 'rotateRightScale', 'rotateTopScale', 'rotateBottomScale' ];
// types of animations for "older" browsers
this.animTypesCond = [ 'fadeInOut', 'slideLeft', 'slideRight', 'slideTop', 'slideBottom' ];
// array containing the animation types to choose from when the options.animType is set to 'random'
this.animTypes = this.animTypesCond;
if( Modernizr.csstransforms3d ) {
this.animTypes = this.animTypesAll;
}
this.animType = this.options.animType;
if( this.animType !== 'random' ) {
if( !Modernizr.csstransforms3d && $.inArray( this.animType, this.animTypesCond ) === -1 && this.animType !== 'showHide' ) {
// fallback to 'fadeInOut' if user sets a type which is not supported
this.animType = 'fadeInOut';
}
}
this.animTypesTotal = this.animTypes.length;
// the <ul> where the items are placed
this.$list = this.$el.children( 'ul' );
// remove img's and add background-image to anchors
// preload the images before
var loaded = 0,
$imgs = this.$list.find( 'img' ),
count = $imgs.length;
$imgs.each( function() {
var $img = $( this ),
src = $img.attr( 'src' );
$( '<img/>' ).load( function() {
++loaded;
$img.parent().css( 'background-image', 'url(' + src + ')' );
if( loaded === count ) {
$imgs.remove();
_self.$el.removeClass( 'ri-grid-loading' );
// the items
_self.$items = _self.$list.children( 'li' );
// make a copy of the items
_self.$itemsCache = _self.$items.clone();
// total number of items
_self.itemsTotal = _self.$items.length;
// the items that will be out of the grid
// actually the item's child (anchor element)
_self.outItems = [];
_self._layout();
_self._initEvents();
// replace "options.step" items after "options.interval" time
// the items that go out are randomly chosen, while the ones that get in
// respect the order (first in first out)
_self._start();
}
} ).attr( 'src', src )
} );
},
_layout : function( callback ) {
var _self = this;
// sets the grid dimentions based on the container's width
this._setGridDim();
// reset
this.$list.empty();
this.$items = this.$itemsCache.clone().appendTo( this.$list );
var $outItems = this.$items.filter( ':gt(' + ( this.showTotal - 1 ) + ')' ),
$outAItems = $outItems.children( '.grid-image-wrap' );
this.outItems.length = 0;
$outAItems.each( function( i ) {
_self.outItems.push( $( this ) );
} );
$outItems.remove();
// container's width
var containerWidth = ( document.defaultView ) ? parseInt( document.defaultView.getComputedStyle( this.$el.get( 0 ), null ).width ) : this.$el.width(),
// item's width
itemWidth = Math.floor( containerWidth / this.columns ),
// calculate gap
gapWidth = containerWidth - ( this.columns * Math.floor( itemWidth ) );
for( var i = 0; i < this.rows; ++i ) {
for( var j = 0; j < this.columns; ++j ) {
var $item = this.$items.eq( this.columns * i + j ),
h = itemWidth,
w = ( j < Math.floor( gapWidth ) ) ? itemWidth + 1 : itemWidth;
$item.css( {
width : w,
height : h
} );
/*
if( gapWidth % 1 !== 0 && j === this.columns - 1 ) {
$item.children( 'a' ).css( {
width : '+=1'//,
height : '+=1'
} );
}
*/
}
}
if( this.options.preventClick ) {
this.$items.children().css( 'cursor', 'default' ).on( 'click.gridrotator', false );
}
if( callback ) {
callback.call();
}
},
// set the grid rows and columns
_setGridDim : function() {
// container's width
var c_w = this.$el.width();
// we will choose the number of rows/columns according to the container's width and the values set on the plugin options
switch( true ) {
case ( c_w < 240 ) : this.rows = this.options.w240.rows; this.columns = this.options.w240.columns; break;
case ( c_w < 320 ) : this.rows = this.options.w320.rows; this.columns = this.options.w320.columns; break;
case ( c_w < 480 ) : this.rows = this.options.w480.rows; this.columns = this.options.w480.columns; break;
case ( c_w < 768 ) : this.rows = this.options.w768.rows; this.columns = this.options.w768.columns; break;
case ( c_w < 1024 ) : this.rows = this.options.w1024.rows; this.columns = this.options.w1024.columns; break;
default : this.rows = this.options.rows; this.columns = this.options.columns; break;
}
this.showTotal = this.rows * this.columns;
},
// init window resize event
_initEvents : function() {
var _self = this;
$window.on( 'debouncedresize.gridrotator', function( event ) {
clearTimeout( _self.playtimeout );
_self._layout( function() {
_self._start();
} );
} );
},
// start rotating elements
_start : function() {
if( this.showTotal < this.itemsTotal ) {
this._showNext();
}
},
// get which type of animation
_getAnimType : function() {
if( this.animType === 'random' ) {
return this.animTypes[ Math.floor( Math.random() * this.animTypesTotal ) ];
}
else {
return this.animType;
}
},
// get css properties for the transition effect
_getAnimProperties : function( $in, $out ) {
var startInProp = {},
startOutProp = {},
endInProp = {},
endOutProp = {},
animType = this._getAnimType(),
speed;
switch( animType ) {
case 'showHide' :
speed = 0;
endOutProp.opacity = 0;
break;
case 'fadeInOut' :
endOutProp.opacity = 0;
break;
case 'slideLeft' :
startInProp.left = $out.width();
endInProp.left = 0;
endOutProp.left = -$out.width();
break;
case 'slideRight' :
startInProp.left = -$out.width();
endInProp.left = 0;
endOutProp.left = $out.width();
break;
case 'slideTop' :
startInProp.top = $out.height();
endInProp.top = 0;
endOutProp.top = -$out.height();
break;
case 'slideBottom' :
startInProp.top = -$out.height();
endInProp.top = 0;
endOutProp.top = $out.height();
break;
case 'rotateLeft' :
speed = this.options.animSpeed / 2;
startInProp.rotateY = '90deg';
endInProp.rotateY = '0deg';
endInProp.delay = speed;
endOutProp.rotateY = '-90deg';
break;
case 'rotateRight' :
speed = this.options.animSpeed / 2;
startInProp.rotateY = '-90deg';
endInProp.rotateY = '0deg';
endInProp.delay = speed;
endOutProp.rotateY = '90deg';
break;
case 'rotateTop' :
speed = this.options.animSpeed / 2;
startInProp.rotateX = '90deg';
endInProp.rotateX = '0deg';
endInProp.delay = speed;
endOutProp.rotateX = '-90deg';
break;
case 'rotateBottom' :
speed = this.options.animSpeed / 2;
startInProp.rotateX = '-90deg';
endInProp.rotateX = '0deg';
endInProp.delay = speed;
endOutProp.rotateX = '90deg';
break;
case 'scale' :
speed = this.options.animSpeed / 2;
startInProp.scale = '0';
startOutProp.scale = '1';
endInProp.scale = '1';
endInProp.delay = speed;
endOutProp.scale = '0';
break;
case 'rotateLeftScale' :
startInProp.scale = '0.3';
startOutProp.scale = '1';
endInProp.scale = '1';
endOutProp.scale = '0.3';
speed = this.options.animSpeed / 2;
startInProp.rotateY = '90deg';
endInProp.rotateY = '0deg';
endInProp.delay = speed;
endOutProp.rotateY = '-90deg';
break;
case 'rotateRightScale' :
startInProp.scale = '0.3';
startOutProp.scale = '1';
endInProp.scale = '1';
endOutProp.scale = '0.3';
speed = this.options.animSpeed / 2;
startInProp.rotateY = '-90deg';
endInProp.rotateY = '0deg';
endInProp.delay = speed;
endOutProp.rotateY = '90deg';
break;
case 'rotateTopScale' :
startInProp.scale = '0.3';
startOutProp.scale = '1';
endInProp.scale = '1';
endOutProp.scale = '0.3';
speed = this.options.animSpeed / 2;
startInProp.rotateX = '90deg';
endInProp.rotateX = '0deg';
endInProp.delay = speed;
endOutProp.rotateX = '-90deg';
break;
case 'rotateBottomScale' :
startInProp.scale = '0.3';
startOutProp.scale = '1';
endInProp.scale = '1';
endOutProp.scale = '0.3';
speed = this.options.animSpeed / 2;
startInProp.rotateX = '-90deg';
endInProp.rotateX = '0deg';
endInProp.delay = speed;
endOutProp.rotateX = '90deg';
break;
case 'rotate3d' :
speed = this.options.animSpeed / 2;
startInProp.rotate3d = '1, 1, 0, 90deg';
endInProp.rotate3d = '1, 1, 0, 0deg';
endInProp.delay = speed;
endOutProp.rotate3d = '1, 1, 0, -90deg';
break;
}
var animSpeed = ( speed != undefined ) ? speed : this.options.animSpeed;
return {
startInProp : startInProp,
startOutProp : startOutProp,
endInProp : endInProp,
endOutProp : endOutProp,
animSpeed : animSpeed
};
},
// show next "option.step" elements
_showNext : function( t ) {
var _self = this;
clearTimeout( this.playtimeout );
this.playtimeout = setTimeout( function() {
var step = _self.options.step,
max = _self.options.maxStep,
min = 1;
if( max > _self.showTotal ) {
max = _self.showTotal;
}
var $items = _self.$items,
outs = [],
// number of items to swith at this point of time
nmbOut = ( step === 'random' ) ? Math.floor( Math.random() * max + min ) : Math.min( Math.abs( step ) , max ) ,
// array with random indexes. These will be the indexes of the items we will replace
randArr = _self._getRandom( nmbOut, _self.showTotal );
for( var i = 0; i < nmbOut; ++i ) {
// element to go out
var $out = $items.eq( randArr[ i ] );
// if element is active, which means it is currently animating,
// then we need to get different positions..
if( $out.data( 'active' ) ) {
// one of the items is active, call again..
_self._showNext( 1 );
return false;
}
// add it to outs array
outs.push( $out );
}
for( var i = 0; i < nmbOut; ++i ) {
var $out = outs[ i ],
$outA = $out.children( '.grid-image-wrap:last' ),
newElProp = {
width : $outA.width(),
height : $outA.height()
};
// element stays active
$out.data( 'active', true );
// get the element (anchor) that will go in (first one inserted in _self.outItems)
var $inA = _self.outItems.shift();
// save element that went out
_self.outItems.push( $outA.clone() );
// prepend in element
$inA.css( newElProp ).prependTo( $out );
var animProp = _self._getAnimProperties( $inA, $outA );
if( Modernizr.csstransitions ) {
$inA.css( animProp.startInProp ).transition( animProp.endInProp, animProp.animSpeed, _self.options.animEasingIn );
$outA.css( animProp.startOutProp ).transition( animProp.endOutProp, animProp.animSpeed, _self.options.animEasingOut, function() {
$( this ).parent().data( 'active', false ).end().remove();
} );
}
// fallback to jQuery animate
else {
$inA.css( animProp.startInProp ).stop().animate( animProp.endInProp, animProp.animSpeed );
$outA.css( animProp.startOutProp ).stop().animate( animProp.endOutProp, animProp.animSpeed, function() {
$( this ).parent().data( 'active', false ).end().remove();
} )
}
}
// again and again..
_self._showNext();
}, t || Math.max( Math.abs( this.options.interval ) , 300 ) );
},
_getRandom : function( cnt, limit ) {
var randArray = [];
for( var i = 0; i < limit; ++i ) {
randArray.push( i )
}
return randArray.shuffle().slice(0,cnt);
}
};
var logError = function( message ) {
if ( window.console ) {
window.console.error( message );
}
};
$.fn.gridrotator = function( options ) {
if ( typeof options === 'string' ) {
var args = Array.prototype.slice.call( arguments, 1 );
this.each(function() {
var instance = $.data( this, 'gridrotator' );
if ( !instance ) {
logError( "cannot call methods on gridrotator prior to initialization; " +
"attempted to call method '" + options + "'" );
return;
}
if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
logError( "no such method '" + options + "' for gridrotator instance" );
return;
}
instance[ options ].apply( instance, args );
});
}
else {
this.each(function() {
var instance = $.data( this, 'gridrotator' );
if ( instance ) {
instance._init();
}
else {
$.data( this, 'gridrotator', new $.GridRotator( options, this ) );
}
});
}
return this;
};
} )( jQuery, window );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment