/* * Moving Boxes v2.1.1 * by Chris Coyier * http://css-tricks.com/moving-boxes/ */ (function($){ $.movingBoxes = function(el, options){ // To avoid scope issues, use 'base' instead of 'this' // to reference this class from internal events and functions. var base = this; // Access to jQuery and DOM versions of element base.$el = $(el).addClass('mb-slider'); base.el = el; // Add a reverse reference to the DOM object base.$el.data('movingBoxes', base); base.init = function(){ base.options = $.extend({}, $.movingBoxes.defaultOptions, options); // Setup formatting (to reduce the amount of initial HTML) base.$el.wrap('
'); // defaults base.$window = base.$el.parent(); // mb-scroll base.$wrap = base.$window.parent() // mb-wrapper .css({ width : base.options.width }) // override css width .prepend('') .append('
'); base.$panels = base.$el.children().addClass('mb-panel'); base.runTime = $('.mb-slider').index(base.$el) + 1; // Get index (run time) of this slider on the page base.regex = new RegExp('slider' + base.runTime + '=(\\d+)', 'i'); // hash tag regex base.initialized = false; base.currentlyMoving = false; base.curPanel = 1; // code to run to update MovingBoxes when the number of panels change base.update(); $(window).load(function(){ base.update(false); }); // animate height after all images load // Set up click on left/right arrows base.$wrap.find('.mb-right').click(function(){ base.goForward(); return false; }).end().find('.mb-left').click(function(){ base.goBack(); return false; }); // go to clicked panel base.$el.delegate('.mb-panel', 'click', function(){ base.change( base.$panels.index($(this)) + 1 ); }); // Activate moving box on click or when an internal link obtains focus base.$wrap.click(function(){ base.active(); }); base.$panels.delegate('a', 'focus' ,function(){ // focused link centered in moving box var loc = base.$panels.index($(this).closest('.mb-panel')) + 1; if (loc !== base.curPanel){ base.change( base.$panels.index($(this).closest('.mb-panel')) + 1, {}, false ); } }); // Add keyboard navigation $(document).keyup(function(e){ // ignore arrow/space keys if inside a form element if (e.target.tagName.match('TEXTAREA|INPUT|SELECT')) { return; } switch (e.which) { case 39: case 32: // right arrow & space if (base.$wrap.is('.mb-active-slider')){ base.goForward(); } break; case 37: // left arrow if (base.$wrap.is('.mb-active-slider')){ base.goBack(); } break; } }); // Set up "Current" panel var startPanel = (base.options.hashTags) ? base.getHash() || base.options.startPanel : base.options.startPanel; // Bind Events $.each('initialized.movingBoxes initChange.movingBoxes beforeAnimation.movingBoxes completed.movingBoxes'.split(' '), function(i,o){ var evt = o.split('.')[0]; if ($.isFunction(base.options[evt])){ base.$el.bind(o, base.options[evt]); } }); // animate to chosen start panel - starting from the first panel makes it look better setTimeout(function(){ base.change(startPanel, function(){ base.initialized = true; base.$el.trigger( 'initialized.movingBoxes', [ base, startPanel ] ); }); }, base.options.speed * 2 ); }; // update the panel, flag is used to prevent events from firing base.update = function(flag){ var t; // Set up panes & content sizes; default: panelWidth = 50% of entire width base.$panels = base.$el.children() .addClass('mb-panel') .css({ width : base.options.width * base.options.panelWidth, margin: 0 }) // inner wrap of each panel .each(function(){ if ($(this).find('.mb-inside').length === 0) { $(this).wrapInner('
'); } }); base.totalPanels = base.$panels.length; // save 'cur' numbers (current larger panel size), use stored sizes if they exist t = base.$panels.eq(base.curPanel - 1); base.curWidth = base.curWidth || t.outerWidth(); // save 'reg' (reduced size) numbers base.regWidth = base.curWidth * base.options.reducedSize; // set image heights so base container height is correctly set base.$panels.css({ width: base.curWidth, fontSize: '1em' }); // make all panels big // save each panel height... script will resize container as needed // make sure current panel css is applied before measuring base.$panels.eq(base.curPanel-1).addClass(base.options.currentPanel); base.heights = base.$panels.map(function(i,e){ return $(e).outerHeight(true); }).get(); base.returnToNormal(base.curPanel, 0); // resize new panel, animation time base.growBigger(base.curPanel, 0, flag); // make base container wide enough to contain all the panels base.$el.css({ position : 'absolute', // add a bit more width to each box (100px should cover margin/padding, etc; then add 1/2 overall width in case only one panel exists width : (base.curWidth + 100) * base.totalPanels + (base.options.width - base.curWidth) / 2, height : Math.max.apply( this, base.heights ) + 10 }); base.$window.css({ height : (base.options.fixedHeight) ? Math.max.apply( this, base.heights ) : base.heights[base.curPanel-1] }); // add padding so scrollLeft = 0 centers the left-most panel (needed because scrollLeft cannot be < 0) base.$panels.eq(0).css({ 'margin-left' : (base.options.width - base.curWidth) / 2 }); base.buildNav(); base.change(base.curPanel, {}, false); // initialize from first panel... then scroll to start panel }; // Creates the numbered navigation links base.buildNav = function() { base.$navLinks = {}; if (base.$nav) { base.$nav.remove(); } if (base.options.buildNav && (base.totalPanels > 1)) { base.$nav = $('
').appendTo(base.$wrap); var j, a = '', navFormat = $.isFunction(base.options.navFormatter), // need link in place to get CSS properties hiddenText = parseInt( base.$nav.find('.mb-testing').css('text-indent'), 10) < 0; base.$panels.each(function(i) { j = i + 1; a += '' + tmp + ' '; // Add formatting to title attribute if text is hidden } else { a += '">' + j + ' '; } }); base.$navLinks = base.$nav .html(a) .find('a').bind('click', function() { base.change( base.$navLinks.index($(this)) + 1 ); return false; }); } }; // Resize panels to normal base.returnToNormal = function(num, time){ var panels = base.$panels.not(':eq(' + (num-1) + ')').removeClass(base.options.currentPanel); if (base.options.reducedSize === 1) { panels.css({ width: base.regWidth }); // excluding fontsize change to prevent video flicker } else { panels.animate({ width: base.regWidth, fontSize: base.options.reducedSize + 'em' }, (time === 0) ? time : base.options.speed); } }; // Zoom in on selected panel base.growBigger = function(num, time, flag){ var panels = base.$panels.eq(num-1); if (base.options.reducedSize === 1) { panels.css({ width: base.curWidth }); // excluding fontsize change to prevent video flicker if (base.initialized) { base.completed(num, flag); } } else { panels.animate({ width: base.curWidth, fontSize: '1em' }, (time === 0) ? time : base.options.speed, function(){ // completed event trigger // even though animation is not queued, trigger is here because it is the last animation to complete if (base.initialized) { base.completed(num, flag); } }); } }; base.completed = function(num, flag){ $(this).addClass(base.options.currentPanel); // add current panel class after animating in case it has sizing parameters if (flag !== false) { base.$el.trigger( 'completed.movingBoxes', [ base, num ] ); } }; // go forward/back base.goForward = function(){ if (base.initialized) { base.change(base.curPanel + 1); } }; base.goBack = function(){ if (base.initialized) { base.change(base.curPanel - 1); } }; // Change view to display selected panel base.change = function(curPanel, callback, flag){ // make sure it's a number and not a string curPanel = parseInt(curPanel, 10); if (base.initialized) { // make this moving box active base.active(); // initChange event - has extra parameter with targeted panel (not cleaned) base.$el.trigger( 'initChange.movingBoxes', [ base, curPanel ] ); } // psuedo wrap - it's a pain to clone the first & last panel then resize them correctly while wrapping AND make it look good if ( base.options.wrap ) { if ( curPanel < 1 ) { curPanel = base.totalPanels; } if ( curPanel > base.totalPanels ) { curPanel = 1; } } else { if ( curPanel < 1 ) { curPanel = 1; } if ( curPanel > base.totalPanels ) { curPanel = base.totalPanels; } } // don't do anything if it's the same panel if (base.initialized && base.curPanel === curPanel && !flag) { return false; } // abort if panel is already animating if (!base.currentlyMoving) { base.currentlyMoving = true; // center panel in scroll window var ani, leftValue = base.$panels.eq(curPanel-1).position().left - (base.options.width - base.curWidth) / 2; // when scrolling right, add the difference of the larger current panel width if (curPanel > base.curPanel) { leftValue -= ( base.curWidth - base.regWidth ); } ani = (base.options.fixedHeight) ? { scrollLeft : leftValue } : { scrollLeft: leftValue, height: base.heights[curPanel - 1] }; // before animation trigger if (base.initialized) { base.$el.trigger( 'beforeAnimation.movingBoxes', [ base, curPanel ] ); } // animate the panels base.$window.animate( ani, { queue : false, duration : base.options.speed, easing : base.options.easing, complete : function(){ base.curPanel = curPanel; if (base.initialized) { base.$window.scrollTop(0); // Opera fix - otherwise, it moves the focus link to the middle of the viewport } base.currentlyMoving = false; if (typeof(callback) === 'function') { callback(base); } } } ); base.returnToNormal(curPanel); base.growBigger(curPanel); if (base.options.hashTags && base.initialized) { base.setHash(curPanel); } } base.$wrap.find('.mb-controls a') .removeClass(base.options.currentPanel) .eq(curPanel - 1).addClass(base.options.currentPanel); }; // get & set hash tags base.getHash = function(){ var n = window.location.hash.match(base.regex); return (n===null) ? '' : parseInt(n[1],10); }; base.setHash = function(n){ var s = 'slider' + base.runTime + "=", h = window.location.hash; if ( typeof h !== 'undefined' ) { window.location.hash = (h.indexOf(s) > 0) ? h.replace(base.regex, s + n) : h + "&" + s + n; } }; // Make moving box active (for keyboard navigation) base.active = function(el){ $('.mb-active-slider').removeClass('mb-active-slider'); base.$wrap.addClass('mb-active-slider'); }; // get: var currentPanel = $('.slider').data('movingBoxes').currentPanel(); // returns # of currently selected/enlarged panel // set: var currentPanel = $('.slider').data('movingBoxes').currentPanel(2, function(){ alert('done!'); }); // returns and scrolls to 2nd panel base.currentPanel = function(panel, callback){ if (typeof(panel) !== 'undefined') { base.change(panel, callback); // parse in case someone sends a string } return base.curPanel; }; // Run initializer base.init(); }; $.movingBoxes.defaultOptions = { // Appearance startPanel : 2, // start with this panel width : 800, // overall width of movingBoxes panelWidth : 0.5, // current panel width adjusted to 50% of overall width reducedSize : 0.8, // non-current panel size: 80% of panel size fixedHeight : true, // if true, slider height set to max panel height; if false, slider height will auto adjust. // Behaviour speed : 500, // animation time in milliseconds hashTags : true, // if true, hash tags are enabled wrap : true, // if true, the panel will "wrap" (it really rewinds/fast forwards) at the ends buildNav : false, // if true, navigation links will be added navFormatter : null, // function which returns the navigation text for each panel easing : 'swing', // anything other than "linear" or "swing" requires the easing plugin // Selectors & classes currentPanel : 'current', // current panel class tooltipClass : 'tooltip', // added to the navigation, but the title attribute is blank unless the link text-indent is negative // Callbacks initialized : null, // callback when MovingBoxes has completed initialization initChange : null, // callback upon change panel initialization beforeAnimation : null, // callback before any animation occurs completed : null // callback after animation completes }; $.fn.movingBoxes = function(options, callback){ var num, mb = this.data('movingBoxes'); return this.each(function(){ // initialize the slider but prevent multiple initializations if ((typeof(options)).match('object|undefined')){ if (mb) { mb.update(); } else { (new $.movingBoxes(this, options)); } } else if (/\d/.test(options) && !isNaN(options) && mb) { num = (typeof(options) === "number") ? options : parseInt($.trim(options),10); // accepts " 4 " // ignore out of bound panels if ( num >= 1 && num <= mb.totalPanels ) { mb.change(num, callback); // page #, autoplay, one time callback } } }); }; // Return the movingBoxes object $.fn.getMovingBoxes = function(){ return this.data('movingBoxes'); }; })(jQuery);