/**
* jquery.gridrotator.js v1.1.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';
/*
* 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: 100
};
// 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;
};
// HTML5 PageVisibility API
// http://www.html5rocks.com/en/tutorials/pagevisibility/intro/
// by Joe Marini (@joemarini)
function getHiddenProp() {
var prefixes = ['webkit', 'moz', 'ms', 'o'];
// if 'hidden' is natively supported just return it
if ('hidden' in document) return 'hidden';
// otherwise loop over all the known prefixes until we find one
for (var i = 0; i < prefixes.length; i++) {
if ((prefixes[i] + 'Hidden') in document)
return prefixes[i] + 'Hidden';
}
// otherwise it's not supported
return null;
}
function isHidden() {
var prop = getHiddenProp();
if (!prop) return false;
return document[prop];
}
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
// 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');
this._init(options);
}
};
// the options
$.GridRotator.defaults = {
// number of rows
rows: 4,
// number of columns
columns: 10,
w992: {
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',
// change it as you wish..
maxStep: 3,
// prevent user to click the items
preventClick: true,
// animation type
// showHide || fadeInOut ||
// slideLeft || slideRight || slideTop || slideBottom ||
// rotateBottom || rotateLeft || rotateRight || rotateTop ||
// scale ||
// rotate3d ||
// rotateLeftScale || rotateRightScale || rotateTopScale || rotateBottomScale ||
// random
animType: 'random',
// animation speed
animSpeed: 800,
// 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,
// if false the animations will not start
// use false if onhover is true for example
slideshow: true,
// if true the items will switch when hovered
onhover: false,
// ids of elements that shouldn't change
nochange: []
};
$.GridRotator.prototype = {
_init: function(options) {
// options
this.options = $.extend(true, {}, $.GridRotator.defaults, options);
// cache some elements + variables
this._config();
},
_config: function() {
var self = this,
transEndEventNames = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'msTransition': 'MSTransitionEnd',
'transition': 'transitionend'
};
// support CSS transitions and 3d transforms
this.supportTransitions = Modernizr.csstransitions;
this.supportTransforms3D = Modernizr.csstransforms3d;
this.transEndEventName = transEndEventNames[Modernizr.prefixed('transition')] + '.gridrotator';
// all animation types for the random option
this.animTypes = this.supportTransforms3D ? [
'fadeInOut',
'slideLeft',
'slideRight',
'slideTop',
'slideBottom',
'rotateLeft',
'rotateRight',
'rotateTop',
'rotateBottom',
// 'scale',
'rotate3d',
// 'rotateLeftScale',
// 'rotateRightScale',
// 'rotateTopScale',
// 'rotateBottomScale'
] :
['fadeInOut', 'slideLeft', 'slideRight', 'slideTop', 'slideBottom'];
this.animType = this.options.animType;
if (this.animType !== 'random' && !this.supportTransforms3D && $.inArray(this.animType, this.animTypes) === -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
where the items are placed
this.$list = this.$el.children('ul');
// remove images 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');
$('
').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(function() {
self._initEvents();
});
// replace [options.step] items after [options.interval] time
// the items that go out are randomly chosen, while the ones that get in
// follow a "First In First Out" logic
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('a');
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 idx = this.columns * i + j,
$item = this.$items.eq(idx);
$item.css({
width: j < Math.floor(gapWidth) ? itemWidth + 1 : itemWidth,
height: itemWidth
});
if ($.inArray(idx, this.options.nochange) !== -1) {
$item.addClass('ri-nochange').data('nochange', true);
}
}
}
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 in 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 < 992):
this.rows = this.options.w992.rows;
this.columns = this.options.w992.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() {
self._layout();
});
// use the property name to generate the prefixed event name
var visProp = getHiddenProp();
// HTML5 PageVisibility API
// http://www.html5rocks.com/en/tutorials/pagevisibility/intro/
// by Joe Marini (@joemarini)
if (visProp) {
var evtname = visProp.replace(/[H|h]idden/, '') + 'visibilitychange';
document.addEventListener(evtname, function() {
self._visChange();
});
}
if (!Modernizr.touch && this.options.onhover) {
self.$items.on('mouseenter.gridrotator', function() {
var $item = $(this);
if (!$item.data('active') && !$item.data('hovered') && !$item.data('nochange')) {
$item.data('hovered', true);
self._replace($item);
}
}).on('mouseleave.gridrotator', function() {
$(this).data('hovered', false);
});
}
},
_visChange: function() {
isHidden() ? clearTimeout(this.playtimeout) : this._start();
},
// start rotating elements
_start: function() {
if (this.showTotal < this.itemsTotal && this.options.slideshow) {
this._showNext();
}
},
// get which type of animation
_getAnimType: function() {
return this.animType === 'random' ? this.animTypes[Math.floor(Math.random() * this.animTypesTotal)] : this.animType;
},
// get css properties for the transition effect
_getAnimProperties: function($out) {
var startInProp = {},
startOutProp = {},
endInProp = {},
endOutProp = {},
animType = this._getAnimType(),
speed, delay = 0;
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.transform = 'rotateY(90deg)';
endInProp.transform = 'rotateY(0deg)';
delay = speed;
endOutProp.transform = 'rotateY(-90deg)';
break;
case 'rotateRight':
speed = this.options.animSpeed / 2;
startInProp.transform = 'rotateY(-90deg)';
endInProp.transform = 'rotateY(0deg)';
delay = speed;
endOutProp.transform = 'rotateY(90deg)';
break;
case 'rotateTop':
speed = this.options.animSpeed / 2;
startInProp.transform = 'rotateX(90deg)';
endInProp.transform = 'rotateX(0deg)';
delay = speed;
endOutProp.transform = 'rotateX(-90deg)';
break;
case 'rotateBottom':
speed = this.options.animSpeed / 2;
startInProp.transform = 'rotateX(-90deg)';
endInProp.transform = 'rotateX(0deg)';
delay = speed;
endOutProp.transform = 'rotateX(90deg)';
break;
case 'scale':
speed = this.options.animSpeed / 2;
startInProp.transform = 'scale(0)';
startOutProp.transform = 'scale(1)';
endInProp.transform = 'scale(1)';
delay = speed;
endOutProp.transform = 'scale(0)';
break;
case 'rotateLeftScale':
startOutProp.transform = 'scale(1)';
speed = this.options.animSpeed / 2;
startInProp.transform = 'scale(0.3) rotateY(90deg)';
endInProp.transform = 'scale(1) rotateY(0deg)';
delay = speed;
endOutProp.transform = 'scale(0.3) rotateY(-90deg)';
break;
case 'rotateRightScale':
startOutProp.transform = 'scale(1)';
speed = this.options.animSpeed / 2;
startInProp.transform = 'scale(0.3) rotateY(-90deg)';
endInProp.transform = 'scale(1) rotateY(0deg)';
delay = speed;
endOutProp.transform = 'scale(0.3) rotateY(90deg)';
break;
case 'rotateTopScale':
startOutProp.transform = 'scale(1)';
speed = this.options.animSpeed / 2;
startInProp.transform = 'scale(0.3) rotateX(90deg)';
endInProp.transform = 'scale(1) rotateX(0deg)';
delay = speed;
endOutProp.transform = 'scale(0.3) rotateX(-90deg)';
break;
case 'rotateBottomScale':
startOutProp.transform = 'scale(1)';
speed = this.options.animSpeed / 2;
startInProp.transform = 'scale(0.3) rotateX(-90deg)';
endInProp.transform = 'scale(1) rotateX(0deg)';
delay = speed;
endOutProp.transform = 'scale(0.3) rotateX(90deg)';
break;
case 'rotate3d':
speed = this.options.animSpeed / 2;
startInProp.transform = 'rotate3d( 1, 1, 0, 90deg )';
endInProp.transform = 'rotate3d( 1, 1, 0, 0deg )';
delay = speed;
endOutProp.transform = 'rotate3d( 1, 1, 0, -90deg )';
break;
}
return {
startInProp: startInProp,
startOutProp: startOutProp,
endInProp: endInProp,
endOutProp: endOutProp,
delay: delay,
animSpeed: speed != undefined ? speed : this.options.animSpeed
};
},
// show next [option.step] elements
_showNext: function(time) {
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;
}
// number of items to swith at this point of time
var 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 = self.$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') || $out.data('nochange')) {
// one of the items is active, call again..
self._showNext(1);
return false;
}
self._replace($out);
}
// again and again..
self._showNext();
}, time || Math.max(Math.abs(this.options.interval), 300));
},
_replace: function($out) {
$out.data('active', true);
var self = this,
$outA = $out.children('a: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 this.outItems)
var $inA = this.outItems.shift();
// save element that went out
this.outItems.push($outA.clone().css('transition', 'none'));
// prepend in element
$inA.css(newElProp).prependTo($out);
var animProp = this._getAnimProperties($outA);
$inA.css(animProp.startInProp);
$outA.css(animProp.startOutProp);
this._setTransition($inA, 'all', animProp.animSpeed, animProp.delay, this.options.animEasingIn);
this._setTransition($outA, 'all', animProp.animSpeed, 0, this.options.animEasingOut);
this._applyTransition($inA, animProp.endInProp, animProp.animSpeed, function() {
var $el = $(this),
t = animProp.animSpeed === self.options.animSpeed && isEmpty(animProp.endInProp) ? animProp.animSpeed : 0;
setTimeout(function() {
if (self.supportTransitions) {
$el.off(self.transEndEventName);
}
$el.next().remove();
$el.parent().data('active', false);
}, t);
}, animProp.animSpeed === 0 || isEmpty(animProp.endInProp));
this._applyTransition($outA, animProp.endOutProp, animProp.animSpeed);
},
_getRandom: function(cnt, limit) {
var randArray = [];
for (var i = 0; i < limit; ++i) {
randArray.push(i)
}
return randArray.shuffle().slice(0, cnt);
},
_setTransition: function(el, prop, speed, delay, easing) {
setTimeout(function() {
el.css('transition', prop + ' ' + speed + 'ms ' + delay + 'ms ' + easing);
}, 25);
},
_applyTransition: function(el, styleCSS, speed, fncomplete, force) {
var self = this;
setTimeout(function() {
$.fn.applyStyle = self.supportTransitions ? $.fn.css : $.fn.animate;
if (fncomplete && self.supportTransitions) {
el.on(self.transEndEventName, fncomplete);
if (force) {
fncomplete.call(el);
}
}
fncomplete = fncomplete || function() {
return false;
};
el.stop().applyStyle(styleCSS, $.extend(true, [], {
duration: speed + 'ms',
complete: fncomplete
}));
}, 25);
}
};
var logError = function(message) {
if (window.console) {
window.console.error(message);
}
};
$.fn.gridrotator = function(options) {
var instance = $.data(this, 'gridrotator');
if (typeof options === 'string') {
var args = Array.prototype.slice.call(arguments, 1);
this.each(function() {
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() {
if (instance) {
instance._init();
} else {
instance = $.data(this, 'gridrotator', new $.GridRotator(options, this));
}
});
}
return instance;
};
})(jQuery, window);