1/*! 2 * Moving Boxes v2.3.4 3 * by Chris Coyier 4 * http://css-tricks.com/moving-boxes/ 5 */ 6/*jshint browser:true, jquery:true */ 7;(function($){ 8 "use strict"; 9 $.movingBoxes = function(el, options){ 10 // To avoid scope issues, use 'base' instead of 'this' 11 // to reference this class from internal events and functions. 12 var o, base = this; 13 14 // Access to jQuery and DOM versions of element 15 base.$el = $(el).addClass('mb-slider'); 16 base.el = el; 17 18 // Add a reverse reference to the DOM object 19 base.$el.data('movingBoxes', base); 20 21 base.init = function(){ 22 base.options = o = $.extend({}, $.movingBoxes.defaultOptions, options); 23 24 // Setup formatting (to reduce the amount of initial HTML) 25 base.$el.wrap('<div class="movingBoxes mb-wrapper"><div class="mb-scroll" /></div>'); 26 27 // defaults 28 base.$window = base.$el.parent(); // mb-scroll 29 base.$wrap = base.$window.parent() // mb-wrapper 30 .prepend('<a class="mb-scrollButtons mb-left"></a>') 31 .append('<a class="mb-scrollButtons mb-right"></a><div class="mb-left-shadow"></div><div class="mb-right-shadow"></div>'); 32 33 base.$panels = base.$el.children().addClass('mb-panel'); 34 base.runTime = $('.mb-slider').index(base.$el) + 1; // Get index (run time) of this slider on the page 35 base.regex = new RegExp('slider' + base.runTime + '=(\\d+)', 'i'); // hash tag regex 36 37 base.initialized = false; 38 base.currentlyMoving = false; 39 base.curPanel = (o.initAnimation) ? 1 : base.getHash() || o.startPanel; 40 // save original slider width 41 base.width = (o.width) ? parseInt(o.width,10) : base.$el.width(); 42 // save panel width, o.panelWidth originally a fraction (0.5 of o.width) if defined, or get first panel width 43 // now can be set after initialization to resize using fraction (value <= 2) or px (all values > 2) 44 base.pWidth = (o.panelWidth) ? (o.panelWidth <=2 ? o.panelWidth * base.width : o.panelWidth) : base.$panels.eq(0).width(); 45 46 // Set up click on left/right arrows 47 base.$left = base.$wrap.find('.mb-left').click(function(){ 48 base.goBack(); 49 return false; 50 }); 51 base.$right = base.$wrap.find('.mb-right').click(function(){ 52 base.goForward(); 53 return false; 54 }); 55 // code to run to update MovingBoxes when the number of panels change 56 base.update({}, false); 57 // make sure current panel is centered 58 base.setWrap(base.curPanel); 59 // go to clicked panel 60 base.$el.delegate('.mb-panel', 'click', function(e){ 61 if (!$(this).hasClass(o.currentPanel)) { 62 e.preventDefault(); // prevent non-current panel links from working 63 base.change( base.$panels.index($(this)) + base.adj, {}, true ); 64 } 65 }); 66 67 // Activate moving box on click or when an internal link obtains focus 68 base.$wrap.click(function(){ 69 if (!base.$wrap.hasClass('mb-active-slider')) { 70 base.active(); 71 } 72 }); 73 base.$panels.delegate('a', 'focus' ,function(e){ 74 e.preventDefault(); 75 // focused link centered in moving box 76 var loc = base.$panels.index($(this).closest('.mb-panel')) + base.adj; 77 if (loc !== base.curPanel){ 78 base.change( loc, {}, true ); 79 } 80 }); 81 82 // Add keyboard navigation 83 $(document).keyup(function(e){ 84 // ignore arrow/space keys if inside a form element 85 if (e.target.tagName.match('TEXTAREA|INPUT|SELECT')) { return; } 86 switch (e.which) { 87 case 39: case 32: // right arrow & space 88 if (base.$wrap.is('.mb-active-slider')){ 89 base.goForward(); 90 } 91 break; 92 case 37: // left arrow 93 if (base.$wrap.is('.mb-active-slider')){ 94 base.goBack(); 95 } 96 break; 97 } 98 }); 99 100 // Bind Events 101 $.each('preinit initialized initChange beforeAnimation completed'.split(' '), function(i,evt){ 102 if ($.isFunction(o[evt])){ 103 base.$el.bind(evt + '.movingBoxes', o[evt]); 104 } 105 }); 106 107 base.$el.trigger( 'preinit.movingBoxes', [ base, base.curPanel ] ); 108 109 }; 110 111 // update the panel, flag is used to prevent events from firing 112 base.update = function(callback, flag){ 113 114 // Infinite loop 115 base.$el.children('.cloned').remove(); 116 base.$panels = base.$el.children();
117 base.adj = (o.wrap && base.$panels.length > 1) ? 0 : 1; // count adjustment for infinite panels 118 119 base.width = (o.width) ? parseInt(o.width,10) : base.width; 120 base.$wrap.css('width', base.width); // set wrapper width 121 122 if (o.wrap && base.$panels.length > 1) { 123 base.$el.prepend( base.$panels.filter(':last').clone().addClass('cloned') ); 124 base.$el.append( base.$panels.filter(':first').clone().addClass('cloned') ); 125 base.$el.find('.cloned').each(function(){ 126 // disable all focusable elements in cloned panels to prevent shifting the panels by tabbing 127 $(this).find('a,input,textarea,select,button,area').removeAttr('name').attr('disabled', 'disabled'); 128 $(this).find('[id]').andSelf().removeAttr('id'); 129 }); 130 } 131 132 // Set up panes & content sizes 133 // defined $panels again to include cloned panels 134 base.$panels = base.$el.children() 135 .addClass('mb-panel') 136 // inner wrap of each panel 137 .each(function(){ 138 if ($(this).find('.mb-inside').length === 0) { 139 $(this).wrapInner('<div class="mb-inside" />'); 140 } 141 }); 142 base.totalPanels = base.$panels.filter(':not(.cloned)').length; // don't include cloned panels in total 143 // in case current panel no longer exists 144 if (base.totalPanels <= 1) { base.curPanel = 1; } 145 146 base.setSizes(flag); 147 148 base.buildNav(); 149 150 base.change(base.curPanel, callback, flag); // initialize from first panel... then scroll to start panel 151 152 // check panel height after all images load 153 base.imagesLoaded(function(){ 154 base.setSizes(false); 155 base.setWrap(base.curPanel); 156 157 // animate to chosen start panel - starting from the first panel makes it look better 158 if (!base.initialized){ 159 setTimeout(function(){ 160 base.initialized = true; 161 base.change(base.getHash() || o.startPanel, {}, false);
162 base.$el.trigger( 'initialized.movingBoxes', [ base, base.curPanel ] ); 163 }, o.speed * 2 ); 164 } 165 166 }); 167 168 }; 169 170 base.setSizes = function(flag){ 171 // include padding & margins around the panels 172 base.padding = parseInt(base.$panels.css('padding-left'), 10) + parseInt(base.$panels.css('margin-left'), 10); 173 174 // save 'cur' numbers (current larger panel size), use stored sizes if they exist 175 base.curWidth = (o.panelWidth) ? (o.panelWidth <=2 ? o.panelWidth * base.width : o.panelWidth) : base.pWidth; 176 // save 'reg' (reduced size) numbers 177 base.regWidth = base.curWidth * o.reducedSize; 178 // set image heights so base container height is correctly set 179 base.$panels.css({ width: base.curWidth, fontSize: '1em' }); // make all panels big 180 // save each panel height... script will resize container as needed 181 // make sure current panel css is applied before measuring 182 base.$panels.eq(base.curPanel - base.adj).addClass(o.currentPanel); 183 base.heights = base.$panels.css('height','auto').map(function(i,e){ return $(e).outerHeight(true); }).get(); 184 185 base.returnToNormal(base.curPanel, 0); // resize new panel, animation time 186 base.growBigger(base.curPanel, 0, flag); 187 base.updateArrows(base.curPanel); 188 189 // make base container wide enough to contain all the panels 190 base.$el.css({ 191 position : 'absolute', 192 // add a bit more width to each box (base.padding *2; then add 1/2 overall width in case only one panel exists) 193 width : (base.curWidth + base.padding * 2) * base.$panels.length + (base.width - base.curWidth) / 2, 194 height : Math.max.apply( this, base.heights ) + 10, 195 // add padding so scrollLeft = 0 centers the left-most panel (needed because scrollLeft cannot be < 0) 196 'padding-left' : (base.width - base.curWidth) / 2 197 }); 198 base.$window.css({ height : (o.fixedHeight) ? Math.max.apply( this, base.heights ) : base.heights[base.curPanel - base.adj] }); 199 }; 200 201 // Creates the numbered navigation links 202 base.buildNav = function() { 203 if (base.$nav) { 204 base.$nav.find('.mb-links').empty(); 205 } else { 206 base.$nav = $('<div class="mb-controls"><span class="mb-links"></span></div>').appendTo(base.$wrap); 207 } 208 if (o.buildNav && base.totalPanels > 1) { 209 var t, j, a = '', $a; 210 base.$panels.filter(':not(.cloned)').each(function(i){ 211 j = i + 1; 212 a = '<a class="mb-link mb-panel' + j + '" href="#"></a>'; 213 $a = $(a); 214 // If a formatter function is present, use it 215 if ($.isFunction(o.navFormatter)) { 216 t = o.navFormatter(j, $(this)); 217 if (typeof(t) === "string") { 218 $a.html(t); 219 } else { 220 $a = $('<a/>', t); 221 } 222 } else { 223 $a.html(j); 224 } 225 $a 226 .appendTo(base.$nav.find('.mb-links')) 227 .addClass('mb-link mb-panel' + j) 228 .data('index', j); 229 }); 230 base.$nav 231 .find('a.mb-link').bind('click', function() { 232 base.change( $(this).data('index') ); 233 return false; 234 }); 235 } 236 }; 237 238 // Resize panels to normal 239 base.returnToNormal = function(num, time){ 240 var panels = base.$panels.not(':eq(' + (num - base.adj) + ')').removeClass(o.currentPanel); 241 if (o.reducedSize === 1) { 242 panels.css({ width: base.regWidth }); // excluding fontsize change to prevent video flicker 243 } else { 244 panels.stop(true,false).animate({ width: base.regWidth, fontSize: o.reducedSize + 'em' }, (time === 0) ? 0 : o.speed); 245 } 246 }; 247 248 // Zoom in on selected panel 249 base.growBigger = function(num, time, flag){ 250 var panels = base.$panels.eq(num - base.adj); 251 if (o.reducedSize === 1) { 252 panels.css({ width: base.curWidth }); // excluding fontsize change to prevent video flicker 253 // time delay prevents click outer panel from following links - fixes issue #67 254 setTimeout(function(){ 255 base.completed(num, flag); 256 }, (time === 0) ? 0 : o.speed); 257 } else { 258 panels.stop(true,false).animate({ width: base.curWidth, fontSize: '1em' }, (time === 0) ? 0 : o.speed, function(){ 259 // completed event trigger 260 // even though animation is not queued, trigger is here because it is the last animation to complete 261 base.completed(num, flag); 262 }); 263 } 264 }; 265 266 // instantly center the indicated panel 267 base.setWrap = function(panel){ 268 if (base.totalPanels >= 1) { 269 base.growBigger(panel, 0, false); 270 var leftValue = base.$panels.eq(panel - base.adj).position().left - (base.width - base.curWidth) / 2 + base.padding; 271 base.$window.scrollLeft(leftValue); 272 } 273 }; 274 275 base.completed = function(num, flag){ 276 // add current panel class after animating in case it has sizing parameters 277 var loc = base.$panels.eq(num - base.adj); 278 if (!loc.hasClass('cloned')) { loc.addClass(o.currentPanel); } 279 if (flag !== false) { base.$el.trigger( 'completed.movingBoxes', [ base, num ] ); } 280 }; 281 282 // go forward/back 283 base.goForward = function(callback){ 284 if (base.initialized) { 285 base.change(base.curPanel + 1, callback); 286 } 287 }; 288 289 base.goBack = function(callback){ 290 if (base.initialized) { 291 base.change(base.curPanel - 1, callback); 292 } 293 }; 294 295 // Change view to display selected panel 296 base.change = function(curPanel, callback, flag){ 297 298 if (base.totalPanels < 1) { 299 if (typeof(callback) === 'function') { callback(base); } 300 return; 301 } 302 var ani, leftValue, wrapped = false; 303 flag = flag !== false; 304 305 // check if curPanel is a jQuery selector or object 306 // $('' + curPanel) needed because $(3) = [3], but $('3') = [] 307 if ($('' + curPanel).length || (curPanel instanceof $ && $(curPanel).length)) { 308 curPanel = $(curPanel).closest('.mb-panel').index() + base.adj; 309 } else { 310 // make sure it's a number and not a string 311 curPanel = parseInt(curPanel, 10); 312 } 313 314 if (base.initialized && flag) { 315 // make this moving box active 316 if (!base.$wrap.hasClass('mb-active-slider')) { base.active(); } 317 // initChange event - has extra parameter with targeted panel (not cleaned) 318 base.$el.trigger( 'initChange.movingBoxes', [ base, curPanel ] ); 319 } 320 321 // Make infinite scrolling work 322 if (o.wrap) { 323 if (curPanel > base.totalPanels) { 324 wrapped = true; 325 curPanel = 1; 326 base.returnToNormal(0, 0); 327 base.setWrap(0); 328 } else if (curPanel === 0) { 329 wrapped = false; 330 curPanel = base.totalPanels; 331 base.setWrap(curPanel + 1); 332 } 333 } 334 335 if ( curPanel < base.adj ) { curPanel = (o.wrap) ? base.totalPanels : 1; } 336 if ( curPanel > base.totalPanels - base.adj ) { curPanel = (o.wrap) ? 1 : base.totalPanels; } 337 // abort if panel is already animating 338 // animation callback needed to clear this flag, but there is no animation before base.initialized is set 339 if (base.curPanel !== curPanel && (!base.currentlyMoving || !base.initialized)) { 340 // set animation flag; animation callback will clear this flag 341 base.currentlyMoving = !o.stopAnimation; 342 343 // center panel in scroll window 344 base.$curPanel = base.$panels.eq(curPanel - base.adj); 345 leftValue = base.$curPanel.position().left - (base.width - base.curWidth) / 2 + base.padding; 346 // when scrolling right, add the difference of the larger current panel width 347 if (base.initialized && (curPanel > base.curPanel || wrapped)) { leftValue -= ( base.curWidth - base.regWidth ); } 348 ani = (o.fixedHeight) ? { scrollLeft : leftValue } : { scrollLeft: leftValue, height: base.heights[curPanel - base.adj] }; 349 base.curPanel = curPanel; 350 // before animation trigger 351 if (base.initialized && flag) { base.$el.trigger( 'beforeAnimation.movingBoxes', [ base, curPanel ] ); } 352 353 if (o.delayBeforeAnimate) { 354 // delay starting slide animation 355 setTimeout(function(){ 356 base.animateBoxes(curPanel, ani, flag, callback); 357 }, parseInt(o.delayBeforeAnimate, 10) || 0); 358 } else { 359 base.animateBoxes(curPanel, ani, flag, callback); 360 } 361 } else { 362 base.endAnimation(); 363 } 364 }; 365 366 base.animateBoxes = function(curPanel, ani, flag, callback){ 367 // animate the panels
368 base.$window.scrollTop(0).stop(true,false).animate( ani, 369 { 370 queue : false, 371 duration : o.speed, 372 easing : o.easing, 373 complete : function(){ 374 if (base.initialized) { 375 base.$window.scrollTop(0); // Opera fix - otherwise, it moves the focus link to the middle of the viewport 376 } 377 base.currentlyMoving = false; 378 if (typeof(callback) === 'function') { callback(base); } 379 } 380 } 381 ); 382 383 base.returnToNormal(curPanel); 384 base.growBigger(curPanel, o.speed, flag); 385 base.updateArrows(curPanel); 386 if (o.hashTags && base.initialized) { base.setHash(curPanel); } 387 base.endAnimation(); 388 389 }; 390 391 base.endAnimation = function(){ 392 // Update navigation links 393 if (o.buildNav && base.$nav.length) { 394 base.$nav.find('a.mb-link') 395 .removeClass(o.currentPanel) 396 .eq(base.curPanel - 1).addClass(o.currentPanel); 397 } 398 }; 399 400 base.updateArrows = function(cur){ 401 base.$left.toggleClass(o.disabled, (!o.wrap && cur === base.adj) || base.totalPanels <= 1); 402 base.$right.toggleClass(o.disabled, (!o.wrap && cur === base.totalPanels) || base.totalPanels <= 1); 403 }; 404 405 // This method tries to find a hash that matches an ID and slider-X 406 // If either found, it tries to find a matching item 407 // If that is found as well, then it returns the page number 408 base.getHash = function(){ 409 var h = window.location.hash, 410 i = h.indexOf('&'), 411 n = h.match(base.regex); 412 // test for "/#/" or "/#!/" used by the jquery address plugin - $('#/') breaks jQuery 413 if (n === null && !/^#&/.test(h) && !/#!?\//.test(h)) { 414 // #quote2&panel1-3&panel3-3 415 h = h.substring(0, (i >= 0 ? i : h.length)); 416 // ensure the element is in the same slider 417 n = ($(h).length && $(h).closest('.mb-slider')[0] === base.el) ? $(h).closest('.mb-panel').index() + base.adj : null; 418 } else if (n !== null) { 419 // #&panel1-3&panel3-3 420 n = (o.hashTags) ? parseInt(n[1],10) : null; 421 } 422 return (n > base.totalPanels) ? null : n; 423 }; 424 425 // set hash tags 426 base.setHash = function(n){ 427 var s = 'slider' + base.runTime + "=", 428 h = window.location.hash; 429 if ( typeof h !== 'undefined' ) { 430 window.location.hash = (h.indexOf(s) > 0) ? h.replace(base.regex, s + n) : h + "&" + s + n; 431 } 432 }; 433 434 // Make moving box active (for keyboard navigation) 435 base.active = function(){ 436 $('.mb-active-slider').removeClass('mb-active-slider'); 437 base.$wrap.addClass('mb-active-slider'); 438 }; 439 440 // get: var currentPanel = $('.slider').data('movingBoxes').currentPanel(); // returns # of currently selected/enlarged panel 441 // set: var currentPanel = $('.slider').data('movingBoxes').currentPanel(2, function(){ alert('done!'); }); // returns and scrolls to 2nd panel 442 base.currentPanel = function(panel, callback){ 443 if (typeof(panel) !== 'undefined') { 444 base.change(panel, callback); // parse in case someone sends a string 445 } 446 return base.curPanel; 447 }; 448 449 // based on https://github.com/Mottie/imagesLoaded plugin 450 base.imagesLoaded = function(callback, img) { 451 var i, ic, 452 c = true, // complete flag 453 t = img ? $(img) : base.$panels.find('img'), 454 l = t.length; 455 img = img || []; // array of images that didn't complete 456 for ( i = 0; i < l; i++ ) { 457 if (t[i].tagName === "IMG") { 458 // IE: fileSize property = -1 before image has loaded & if image load error, so if false is returned 459 // 10x, then just assume it's an error & call it complete - it's what Firefox & webkit does 460 ic = ('fileSize' in t[i] && t[i].fileSize < 0 && t[i].count > 10) ? true : t[i].complete; 461 // complete flag, checks previous flag status, complete flag & image height 462 // image height may need to be > 20 (or whatever the line-height is) because the alt text is included 463 c = (c && ic && t[i].height !== 0); // complete flag 464 // save non-complete images for next iteration 465 if (ic === false) { 466 img.push(t[i]); 467 // iteration count for IE 468 t[i].count = (t[i].count || 0) + 1; 469 } 470 } 471 } 472 if (c) { 473 // all complete, run the callback
474 if (typeof callback === "function") { callback(); } 475 } else { 476 // some images not loaded, rinse & repeat 477 setTimeout(function(){ 478 base.imagesLoaded(callback, img); 479 }, 200); 480 } 481 }; 482 483 // Run initializer 484 base.init(); 485 }; 486 487 $.movingBoxes.defaultOptions = { 488 // Appearance 489 startPanel : 1, // start with this panel 490 reducedSize : 0.8, // non-current panel size: 80% of panel size 491 fixedHeight : false, // if true, slider height set to max panel height; if false, slider height will auto adjust. 492 493 // Behaviour 494 initAnimation: true, // if true, movingBoxes will initialize, then animate into the starting slide (if not the first slide) 495 stopAnimation: false, // if true, movingBoxes will force the animation to complete immediately, if the user selects the next panel 496 hashTags : true, // if true, hash tags are enabled 497 wrap : false, // if true, the panel will loop through the panels infinitely 498 buildNav : false, // if true, navigation links will be added 499 navFormatter : null, // function which returns the navigation text for each panel 500 easing : 'swing', // anything other than "linear" or "swing" requires the easing plugin 501 502 // Times 503 speed : 500, // animation time in milliseconds 504 delayBeforeAnimate : 0, // time to delay in milliseconds before MovingBoxes animates to the selected panel 505 506 // Selectors & classes 507 currentPanel : 'current', // current panel class 508 tooltipClass : 'tooltip', // added to the navigation, but the title attribute is blank unless the link text-indent is negative 509 disabled : 'disabled',// class added to arrows that are disabled (left arrow when on first panel, right arrow on last panel) 510 511 // Callbacks 512 preinit : null, // callback after the basic MovingBoxes structure has been built; before "initialized" 513 initialized : null, // callback when MovingBoxes has completed initialization; all images loaded 514 initChange : null, // callback upon change panel initialization 515 beforeAnimation : null, // callback before any animation occurs 516 completed : null // callback after animation completes 517 518 // deprecated options - but still used to keep the plugin backwards compatible 519 // and allow resizing the overall width and panel width dynamically (i.e. on window resize) 520 // width : 800, // overall width of movingBoxes (not including navigation arrows) 521 // panelWidth : 0.5 // current panel width adjusted to 50% of overall width 522 523 }; 524 525 $.fn.movingBoxes = function(options, callback, flag){ 526 var mb; 527 return this.each(function(){ 528 mb = $(this).data('movingBoxes'); 529 // initialize the slider but prevent multiple initializations 530 if ((typeof(options)).match('object|undefined')){ 531 if (mb && options instanceof $ && options.length) { 532 // pass a jQuery object to change panels 533 mb.change(options, callback, flag); 534 } else if (mb) { 535 mb.update(callback, flag); 536 } else { 537 (new $.movingBoxes(this, options)); 538 } 539 } else if (mb) { 540 // page #, autoplay, one time callback, if flag is false then no events triggered and animation time = 0 541 mb.change(options, callback, flag); 542 } 543 }); 544 }; 545 546 // Return the movingBoxes object 547 $.fn.getMovingBoxes = function(){ 548 return this.data('movingBoxes'); 549 }; 550 551})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.