1/*! 2 photobox v1.9.9 3 (c) 2013 Yair Even Or <http://dropthebit.com> 4 5 MIT-style license. 6*/ 7 8 9;(function($, doc, win){ 10 "use strict"; 11 12 var Photobox, photobox, options, images=[], imageLinks, activeImage = -1, activeURL, lastActive, activeType, prevImage, nextImage, thumbsStripe, docElm, APControl, changeImage, 13 transitionend = "transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", 14 isOldIE = !('placeholder' in doc.createElement('input')), 15 noPointerEvents = (function(){ var el = $('<p>')[0]; el.style.cssText = 'pointer-events:auto'; return !el.style.pointerEvents})(), 16 isTouchDevice = false, // assume "false" unless there's a touch 17 thumbsContainerWidth, thumbsTotalWidth, activeThumb = $(), 18 blankImg = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", 19 transformOrigin = getPrefixed('transformOrigin'), 20 transition = getPrefixed('transition'), 21 // Normalize rAF 22 raf = window.requestAnimationFrame 23 || window.webkitRequestAnimationFrame 24 || window.mozRequestAnimationFrame 25 || window.msRequestAnimationFrame 26 || function(cb) { return window.setTimeout(cb, 1000 / 60); }, 27 28 // Preload images 29 preload = {}, preloadPrev = new Image(), preloadNext = new Image(), 30 // DOM elements 31 closeBtn, image, video, prevBtn, nextBtn, thumbsToggler, caption, captionText, pbLoader, autoplayBtn, thumbs, wrapper, 32 33 defaults = { 34 single : false, // if "true" - gallery will only show a single image, with no way to navigate 35 beforeShow : null, // Callback before showing an image 36 afterClose : null, // Callback after closing the gallery 37 getTitle : null, // Callback for getting title 38 loop : true, // Allows to navigate between first and last images 39 thumb : null, // A relative path from the link to the thumbnail (if it's not inside the link) 40 thumbs : true, // Show gallery thumbnails below the presented photo 41 thumbAttr : 'data-src', // Attribute to get the image for the thumbnail from 42 counter : "(A/B)", // Counts which piece of content is being viewed, relative to the total count of items in the photobox set. ["false","String"] 43 title : true, // show the original alt or title attribute of the image's thumbnail. (path to image, relative to the element which triggers photobox) 44 autoplay : false, // should autoplay on first time or not 45 time : 3000, // autoplay interval, in miliseconds (less than 1000 will hide the autoplay button) 46 history : false, // should use history hashing if possible (HTML5 API) 47 hideFlash : true, // Hides flash elements on the page when photobox is activated. NOTE: flash elements must have wmode parameter set to "opaque" or "transparent" if this is set to false 48 zoomable : true, // disable/enable mousewheel image zooming 49 wheelNextPrev : true, // change image using mousewheel left/right 50 keys : { 51 close : [27, 88, 67], // keycodes to close photobox, default: esc (27), 'x' (88), 'c' (67) 52 prev : [37, 80], // keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80) 53 next : [39, 78] // keycodes to navigate to the next image, default: Right arrow (39), 'n' (78) 54 } 55 }, 56 57 // DOM structure 58 overlay = $('<div id="pbOverlay">').append( 59 thumbsToggler = $('<input type="checkbox" id="pbThumbsToggler" checked hidden>'), 60 pbLoader = $('<div class="pbLoader"><b></b><b></b><b></b></div>'), 61 prevBtn = $('<div id="pbPrevBtn" class="prevNext"><b></b></div>').on('click', next_prev), 62 nextBtn = $('<div id="pbNextBtn" class="prevNext"><b></b></div>').on('click', next_prev),
63 wrapper = $('<div class="pbWrapper">').append( // gives Perspective 64 image = $('<img>'), 65 video = $('<div>') 66 ), 67 closeBtn = $('<div id="pbCloseBtn">').on('click', close)[0], 68 autoplayBtn = $('<div id="pbAutoplayBtn">').append( 69 $('<div class="pbProgress">') 70 ), 71 caption = $('<div id="pbCaption">').append( 72 '<label for="pbThumbsToggler" title="thumbnails on/off"></label>', 73 captionText = $('<div class="pbCaptionText">').append('<div class="title"></div><div class="counter">'), 74 thumbs = $('<div>').addClass('pbThumbs') 75 ) 76 ); 77 78 /////////////////////////////////////////////// 79 // Should remove this and use underscore/lodash if possible 80 81 function throttle(callback, duration){ 82 var wait = false; 83 return function(){ 84 if( !wait ){ 85 callback.call(); 86 wait = true; 87 setTimeout(function(){wait = false; }, duration); 88 } 89 } 90 } 91 92 /////////////////////////////////////////////// 93 // Initialization (on DOM ready) 94 95 function prepareDOM(){ 96 noPointerEvents && overlay.hide(); 97 98 $(doc).on('touchstart.testMouse', function(){ 99 $(doc).off('touchstart.testMouse'); 100 isTouchDevice = true; 101 overlay.addClass('mobile'); 102 }); 103 104 autoplayBtn.off().on('click', APControl.toggle); 105 // attach a delegated event on the thumbs container 106 thumbs.off().on('click', 'a', thumbsStripe.click); 107 // if useragent is IE < 10 (user deserves a slap on the face, but I gotta support them still...) 108 isOldIE && overlay.addClass('msie'); 109 110 // cancel prorogation up to the overlay container so it won't close 111 overlay.off().on('click', 'img', function(e){ 112 e.stopPropagation(); 113 }); 114 115 $(doc.body).append(overlay); 116 117 // need this for later: 118 docElm = doc.documentElement; 119 } 120 121 // @param [List of elements to work on, Custom settings, Callback after image is loaded] 122 $.fn.photobox = function(target, settings, callback){ 123 return this.each(function(){ 124 var o, 125 PB_data = $(this).data('_photobox'); 126 127 if( PB_data ){ // don't initiate the plugin more than once on the same element 128 if( target === 'destroy') 129 PB_data.destroy(); 130 131 return this; 132 } 133 134 if( typeof target != 'string' ) 135 target = 'a'; 136 137 if( target === 'prepareDOM' ){ 138 prepareDOM(); 139 return this; 140 } 141 142 // disable contextmenu 143 if (settings.isDisableRightClick) { 144 overlay.off('contextmenu').on('contextmenu', function(e){return false;}); 145 } 146 147 o = $.extend({}, defaults, settings || {}); 148 photobox = new Photobox(o, this, target); 149 150 // Saves the insance on the gallery's target element 151 $(this).data('_photobox', photobox); 152 // add a callback to the specific gallery 153 photobox.callback = callback; 154 }); 155 } 156 157 Photobox = function(_options, object, target){ 158 this.options = $.extend({}, _options); 159 this.target = target; 160 this.selector = $(object || doc); 161 162 this.thumbsList = null; 163 // filter the links which actually HAS an image as a child 164 var filtered = this.imageLinksFilter( this.selector.find(target) ); 165 this.imageLinks = filtered[0]; // Array of jQuery links 166 this.images = filtered[1]; // 2D Array of image URL & title 167 this.init(); 168 }; 169 170 Photobox.prototype = { 171 init : function(){ 172 var that = this; 173 174 if(!that.thumbsList) { 175 that.thumbsList = thumbsStripe.generate.apply(that); 176 } 177 178 this.selector.on('click.photobox', this.target, function(e){ 179 e.preventDefault(); 180 that.open(this); 181 }); 182 183 // if any node was added or removed from the Selector of the gallery 184 this.observerTimeout = null; 185 186 if( this.selector[0].nodeType == 1 ) // observe normal nodes 187 this.observeDOM( this.selector[0], this.onDOMchanges.bind(this)); 188 }, 189 190 onDOMchanges : function(){ 191 var that = this; 192 // use a timeout to prevent more than one DOM change event firing at once, and also to overcome the fact that IE's DOMNodeRemoved is fired BEFORE elements were actually removed 193 clearTimeout(this.observerTimeout); 194 that.observerTimeout = setTimeout( function(){ 195 var filtered = that.imageLinksFilter( that.selector.find(that.target) ), 196 activeIndex = 0, 197 isActiveUrl = false, 198 i; 199 200 // Make sure that ONLY DOM changes in the photobox number of items will trigger a change 201 if(that.imageLinks.length == filtered[0].length) 202 return; 203 204 that.imageLinks = filtered[0]; 205 that.images = filtered[1]; 206 207 // if photobox is opened 208 if( photobox ){ 209 // if gallery which was changed is the currently viewed one
209: 210 if( that.selector == photobox.selector ){ 211 images = that.images; 212 imageLinks = that.imageLinks; 213 // check if the currently VIEWED photo has been detached from a photobox set 214 // if so, remove navigation arrows 215 // TODO: fix the "images" to be an object and not an array. 216 for( i = images.length; i--; ){ 217 if( images[i][0] == activeURL ) 218 isActiveUrl = true; 219 // if not exits any more 220 } 221 // if( isActiveUrl ){ 222 // overlay.removeClass('hasArrows'); 223 // } 224 } 225 } 226 227 // if this gallery has thumbs 228 //if( that.options.thumbs ){ 229 that.thumbsList = thumbsStripe.generate.apply(that); 230 thumbs.html( that.thumbsList ); 231 //} 232 233 if( that.images.length && activeURL && that.options.thumbs ){ 234 activeIndex = that.thumbsList.find('a[href="'+activeURL+'"]').eq(0).parent().index(); 235 236 if( activeIndex == -1 ) 237 activeIndex = 0; 238 239 // updateIndexes(activeIndex); 240 thumbsStripe.changeActive(activeIndex, 0); 241 } 242 }, 50); 243 }, 244 245 open : function(link){ 246 247 var startImage = $.inArray(link, this.imageLinks); 248 // if image link does not exist in the imageLinks array (probably means it's not a valid part of the gallery) 249 if( startImage == -1 ) 250 return false; 251 252 // load the right gallery selector... 253 options = this.options; 254 images = this.images; 255 imageLinks = this.imageLinks; 256 257 photobox = this; 258 this.setup(1); 259 260 261 overlay.on(transitionend, function(){ 262 overlay.off(transitionend).addClass('on'); // class 'on' is set when the initial fade-in of the overlay is done 263 changeImage(startImage, true); 264 }).addClass('show'); 265 266 267 268 if( isOldIE ) 269 overlay.trigger('MSTransitionEnd'); 270 271 return false; 272 273 }, 274 275 imageLinksFilter : function(obj){ 276 var that = this, 277 images = [], 278 caption = {}, 279 captionlink; 280 281 return [obj.filter(function(i){ 282 // search for the thumb inside the link, if not found then see if there's a 'that.settings.thumb' pointer to the thumbnail 283 var link = $(this), 284 thumbImg, 285 thumbSrc = ''; 286 287 caption.content = link[0].getAttribute('title') || ''; 288 289 if( that.options.thumb && typeof that.options.thumb === 'string' ) 290 thumbImg = link.find(that.options.thumb)[0]; 291 292 if( typeof that.options.thumb === 'function' && link && link.length && link.closest) { 293 var funcRetThumbs = that.options.thumb(link); 294 if(funcRetThumbs) { 295 thumbImg = funcRetThumbs; 296 } 297 } 298 299 // try a direct child lookup 300 if( !that.options.thumb || !thumbImg ) 301 thumbImg = link.find('img')[0]; 302 303 // if no img child found in the link 304 if( thumbImg ){ 305 captionlink = thumbImg.getAttribute('data-pb-captionlink'); 306 thumbSrc = thumbImg.getAttribute(that.options.thumbAttr) || thumbImg.getAttribute('src'); 307 308 caption.content = (thumbImg.getAttribute('title') || thumbImg.getAttribute('alt') || ''); 309 } 310 311 312 // if there is a caption link to be added: 313 if( captionlink ){ 314 captionlink = captionlink.split('['); 315 // parse complex links: text[www.site.com] 316 if( captionlink.length == 2 ){ 317 caption.linkText = captionlink[0]; 318 caption.linkHref = captionlink[1].slice(0,-1); 319 } 320 else{ 321 caption.linkText = captionlink; 322 caption.linkHref = captionlink; 323 } 324 caption.content += ' <a href="'+ caption.linkHref +'">' + caption.linkText + '</a>'; 325 } 326 327 images.push( [link[0].href, caption.content, thumbSrc] ); 328 329 return true; 330 }), images]; 331 }, 332 333 //check if DOM nodes were added or removed, to re-build the imageLinks and thumbnails 334 observeDOM : (function(){ 335 var MutationObserver = win.MutationObserver || win.WebKitMutationObserver,
336 eventListenerSupported = win.addEventListener; 337 338 return function(obj, callback){ 339 if( MutationObserver ){ 340 var that = this; 341 // define a new observer 342 var obs = new MutationObserver(function(mutations, observer){ 343 if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) 344 callback(that); 345 }); 346 // have the observer observe for changes in children 347 obs.observe( obj, { childList:true, subtree:true }); 348 } 349 else if( eventListenerSupported ){ 350 obj.addEventListener('DOMNodeInserted', callback.bind(that), false); 351 obj.addEventListener('DOMNodeRemoved', callback.bind(that), false); 352 } 353 } 354 })(), 355 356 // things that should happen every time the gallery opens or closes (some messed up code below..) 357 setup : function (open){ 358 var fn = open ? "on" : "off"; 359 360 // a hack to change the image src to nothing, because you can't do that in CHROME 361 image[0].src = blankImg; 362 363 // thumbs stuff 364 if( options.thumbs ){ 365 if( !isTouchDevice ){ 366 thumbs[fn]('mouseenter.photobox', thumbsStripe.calc) 367 [fn]('mousemove.photobox', thumbsStripe.move); 368 } 369 } 370 371 if( open ){ 372 image.css({'transition':'0s'}).removeAttr('style'); // reset any transition that might be on the element (yes it's ugly) 373 overlay.show(); 374 // Clean up if another gallery was viewed before, which had a thumbsList 375 thumbs 376 .html( this.thumbsList ) 377 .trigger('mouseenter.photobox'); 378 379 if( options.thumbs ){ 380 overlay.addClass('thumbs'); 381 } 382 else{ 383 thumbsToggler.prop('checked', false); 384 overlay.removeClass('thumbs'); 385 } 386 387 // things to hide if there are less than 2 images 388 if( this.images.length < 2 || options.single ) 389 overlay.removeClass('thumbs hasArrows hasCounter hasAutoplay'); 390 else{ 391 overlay.addClass('hasArrows hasCounter') 392 393 // check is the autoplay button should be visible (per gallery) and if so, should it autoplay or not. 394 if( options.time > 1000 ){ 395 overlay.addClass('hasAutoplay'); 396 if( options.autoplay ) 397 APControl.progress.start(); 398 else 399 APControl.pause(); 400 } 401 else 402 overlay.removeClass('hasAutoplay'); 403 } 404 405 options.hideFlash && $('iframe, object, embed').css('visibility', 'hidden'); 406 407 } else { 408 $(win).off('resize.photobox'); 409 } 410 411 $(doc).off("keydown.photobox")[fn]({ "keydown.photobox": keyDown }); 412 413 if( isTouchDevice ){ 414 //overlay.removeClass('hasArrows'); // no need for Arrows on touch-enabled 415 wrapper[fn]('swipe', onSwipe); 416 } 417 418 if( options.zoomable ){ 419 overlay[fn]({"mousewheel.photobox": scrollZoom }); 420 if( !isOldIE) thumbs[fn]({"mousewheel.photobox": thumbsResize }); 421 } 422 423 if( !options.single && options.wheelNextPrev ){ 424 overlay[fn]({"mousewheel.photobox": throttle(wheelNextPrev,1000) }); 425 } 426 }, 427 428 destroy : function(){ 429 options = this.options; 430 this.selector 431 .off('click.photobox', this.target) 432 .removeData('_photobox'); 433 434 close(); 435 } 436 } 437 438 // on touch-devices only 439 function onSwipe(e, Dx, Dy){ 440 if( Dx == 1 ){ 441 image.css({transform:'translateX(25%)', transition:'.2s', opacity:0}
441); 442 setTimeout(function(){ changeImage(prevImage) }, 200); 443 } 444 else if( Dx == -1 ){ 445 image.css({transform:'translateX(-25%)', transition:'.2s', opacity:0}); 446 setTimeout(function(){ changeImage(nextImage) }, 200); 447 } 448 449 if( Dy == 1 ) 450 thumbsToggler.prop('checked', true); 451 else if( Dy == -1 ) 452 thumbsToggler.prop('checked', false); 453 } 454 455 // manage the (bottom) thumbs strip 456 thumbsStripe = (function(){ 457 var containerWidth = 0, 458 scrollWidth = 0, 459 posFromLeft = 0, // Stripe position from the left of the screen 460 stripePos = 0, // When relative mouse position inside the thumbs stripe 461 animated = null, 462 padding, // in percentage to the containerWidth 463 el, $el, ratio, scrollPos, pos; 464 465 return{ 466 // returns a <ul> element which is populated with all the gallery links and thumbs 467 generate : function(){ 468 var thumbsList = $('<ul>'), 469 elements = [], 470 len = this.imageLinks.length, 471 title, thumbSrc, link, type, i; 472 473 for( i = 0; i < len; i++ ){ 474 link = this.imageLinks[i]; 475 476 thumbSrc = this.images[i][2]; 477 // continue if has thumb 478 if( !thumbSrc ) 479 continue; 480 481 title = this.images[i][1]; 482 type = link.rel ? " class='" + link.rel +"'" : ''; 483 elements.push('<li'+ type +'><a href="'+ link.href +'"><img src="'+ thumbSrc +'" alt="" title="'+ $('<div />').html(title).text() +'" /></a></li>'); 484 }; 485 thumbsList.html( elements.join('') ); 486 return thumbsList; 487 }, 488 489 click : function(e){ 490 e.preventDefault(); 491 492 activeThumb.removeClass('active'); 493 $(document).trigger('pboxPreviewClicked'); 494 activeThumb = $(this).parent().addClass('active'); 495 496 var imageIndex = $(this.parentNode).index(); 497 return changeImage(imageIndex, 0, 1); 498 }, 499 500 changeActiveTimeout : null, 501 /** Highlights the thumb which represents the photo and centres the thumbs viewer on it. 502 ** @thumbClick - if a user clicked on a thumbnail, don't center on it 503 */ 504 changeActive : function(index, delay, thumbClick){ 505 if( !options.thumbs ) 506 return; 507 508 var lastIndex = activeThumb.index(); 509 activeThumb.removeClass('active'); 510 activeThumb = thumbs.find('li').eq(index).addClass('active'); 511 512 if( thumbClick || !activeThumb[0] ) return; 513 // set the scrollLeft position of the thumbs list to show the active thumb 514 clearTimeout(this.changeActiveTimeout); 515 // give the images time to to settle on their new sizes (because of css transition) and then calculate the center... 516 this.changeActiveTimeout = setTimeout( 517 function(){ 518 var pos = activeThumb[0].offsetLeft + activeThumb[0].clientWidth/2 - docElm.clientWidth/2; 519 delay ? thumbs.delay(800) : thumbs.stop(); 520 thumbs.animate({scrollLeft: pos}, 500, 'swing'); 521 }, 200); 522 }, 523 524 // calculate the thumbs container width, if the window has been resized 525 calc : function(e){ 526 el = thumbs[0]; 527 528 containerWidth = el.clientWidth; 529 scrollWidth = el.scrollWidth; 530 padding = 0.15 * containerWidth; 531 532 posFromLeft = thumbs.offset().left; 533 stripePos = e.pageX - padding - posFromLeft; 534 pos = stripePos / (containerWidth - padding*2); 535 scrollPos = (scrollWidth - containerWidth ) * pos; 536 537 thumbs.animate({scrollLeft:scrollPos}, 200); 538 539 clearTimeout(animated); 540 animated = setTimeout(function(){ 541 animated = null; 542 }, 200); 543 544 return this; 545 }, 546 547 // move the stripe left or right according to mouse position 548 move : function(e){ 549 // don't move anything until initial movement on 'mouseenter' has finished 550 if( animated ) return; 551 552 var ratio = scrollWidth / containerWidth,
553 stripePos = e.pageX - padding - posFromLeft, // the mouse X position, "normalized" to the carousel position 554 pos, scrollPos; 555 556 if( stripePos < 0) stripePos = 0; // 557 558 pos = stripePos / (containerWidth - padding*2); // calculated position between 0 to 1 559 // calculate the percentage of the mouse position within the carousel 560 scrollPos = (scrollWidth - containerWidth ) * pos; 561 562 raf(function(){ 563 el.scrollLeft = scrollPos; 564 }); 565 } 566 } 567 })(); 568 569 // Autoplay controller 570 APControl = { 571 autoPlayTimer : false, 572 play : function(){ 573 APControl.autoPlayTimer = setTimeout(function(){ changeImage(nextImage) }, options.time); 574 APControl.progress.start(); 575 autoplayBtn.removeClass('play'); 576 APControl.setTitle('Click to stop autoplay'); 577 options.autoplay = true; 578 }, 579 pause : function(){ 580 clearTimeout(APControl.autoPlayTimer); 581 APControl.progress.reset(); 582 autoplayBtn.addClass('play'); 583 APControl.setTitle('Click to resume autoplay'); 584 options.autoplay = false; 585 }, 586 progress : { 587 reset : function(){ 588 autoplayBtn.find('div').removeAttr('style'); 589 setTimeout(function(){ autoplayBtn.removeClass('playing') },200); 590 }, 591 start : function(){ 592 if( !isOldIE) 593 autoplayBtn.find('div').css(transition, options.time+'ms'); 594 autoplayBtn.addClass('playing'); 595 } 596 }, 597 // sets the button Title property 598 setTitle : function(text){ 599 if(text) 600 autoplayBtn.prop('title', text + ' (every ' + options.time/1000 + ' seconds)' ); 601 }, 602 // the button onClick handler 603 toggle : function(e){ 604 e.stopPropagation(); 605 APControl[ options.autoplay ? 'pause' : 'play'](); 606 } 607 } 608 609 function getPrefixed(prop){ 610 var i, s = doc.createElement('p').style, v = ['ms','O','Moz','Webkit']; 611 if( s[prop] == '' ) return prop; 612 prop = prop.charAt(0).toUpperCase() + prop.slice(1); 613 for( i = v.length; i--; ) 614 if( s[v[i] + prop] == '' ) 615 return (v[i] + prop); 616 } 617 618 function keyDown(event){ 619 var code = event.keyCode, ok = options.keys, result; 620 // Prevent default keyboard action (like navigating inside the page) 621 return $.inArray(code, ok.close) >= 0 && close() || 622 $.inArray(code, ok.next) >= 0 && !options.single && loophole(nextImage) || 623 $.inArray(code, ok.prev) >= 0 && !options.single && loophole(prevImage) || true; 624 } 625 626 function wheelNextPrev(e, dY, dX){ 627 if( dX == 1 ) 628 loophole(nextImage); 629 else if( dX == -1 ) 630 loophole(prevImage); 631 } 632 633 634 // serves as a callback for pbPrevBtn / pbNextBtn buttons but also is called on keypress events 635 function next_prev(){ 636 // don't get crazy when user clicks next or prev buttons rapidly 637 //if( !image.hasClass('zoomable') ) 638 // return false; 639 640 var idx = (this.id == 'pbPrevBtn') ? prevImage : nextImage; 641 642 loophole(idx); 643 return false; 644 } 645 646 function updateIndexes(idx){ 647 lastActive = activeImage; 648 activeImage = idx; 649 activeURL = images[idx][0]; 650 prevImage = (activeImage || (options.loop ? images.length : 0)) - 1; 651 nextImage = ((activeImage + 1) % images.length) || (options.loop ? 0 : -1); 652 } 653 654 // check if looping is allowed before changing image/video. 655 // A pre-changeImage function, only for linear changes 656 function loophole(idx){ 657 if( !options.loop ){ 658 var afterLast = activeImage == images.length-1 && idx == nextImage, 659 beforeFirst = activeImage == 0 && idx == prevImage; 660 661 if( afterLast || beforeFirst ) 662 return; 663 } 664 665 changeImage(idx); 666 } 667 668 changeImage = (function(){ 669 var timer; 670 671 return function(imageIndex, firstTime, thumbClick){ 672 // throttle mechanism 673 if( timer ) 674 return; 675 676 timer = setTimeout(function(){ 677 timer = null; 678 }, 150); 679 680 if( !imageIndex || imageIndex < 0 ) 681 imageIndex = 0; 682 683 // hide/show next-prev buttons 684 if( !options.loop ){ 685 nextBtn[ imageIndex == images.length-1 ? 'addClass' : 'removeClass' ]('hide'); 686 prevBtn[ imageIndex == 0 ? 'addClass' : 'removeClass' ]('hide'); 687 } 688 689 // if there's a callback for this point: 690 if( typeof options.beforeShow == "function") 691 options.beforeShow(imageLinks[imageIndex]); 692 693 overlay.removeClass('error').addClass( imageIndex > activeImage ? 'next' : 'prev' ); 694 695 updateIndexes(imageIndex); 696 697 // reset things 698 stop(); 699 video.empty(); 700 preload.onerror = null;
701 image.add(video).data('zoom', 1); 702 703 activeType = imageLinks[imageIndex].rel == 'video' ? 'video' : 'image'; 704 705 // check if current link is a video 706 if( activeType == 'video' ){ 707 if($(imageLinks[activeImage]).attr('href').match(/<iframe(.+)<\/iframe>/i)){ 708 video.html( newVideo(true) ).addClass('hide').attr('data-iframe', 'original_frame'); 709 }else{ 710 video.html( newVideo() ).addClass('hide'); 711 } 712 713 showContent(firstTime); 714 } 715 else{ 716 // give a tiny delay to the preloader, so it won't be showed when images load very quickly 717 var loaderTimeout = setTimeout(function(){ overlay.addClass('pbLoading'); }, 50); 718 719 if( isOldIE ) overlay.addClass('hide'); // should wait for the image onload. just hide the image while old IE display the preloader 720 721 options.autoplay && APControl.progress.reset(); 722 preload = new Image(); 723 preload.onload = function(){ 724 preload.onload = null; 725 726 if( prevImage >= 0 ) preloadPrev.src = images[prevImage][0]; 727 if( nextImage >= 0 ) preloadNext.src = images[nextImage][0]; 728 729 clearTimeout(loaderTimeout); 730 showContent(firstTime); 731 }; 732 preload.onerror = imageError; 733 preload.src = activeURL; 734 } 735 736 // Show Caption text 737 captionText.on(transitionend, captionTextChange).addClass('change'); 738 if( firstTime || isOldIE ) captionTextChange(); 739 740 741 thumbsStripe.changeActive(imageIndex, firstTime, thumbClick); 742 // Save url hash for current image 743 history.save(); 744 } 745 })(); 746 747 function newVideo(iframe){ 748 if(!iframe) { 749 iframe = false 750 } 751 if(iframe){ 752 var url = $(imageLinks[activeImage]).attr('href'); 753 return url; 754 } 755 756 var url = images[activeImage][0], 757 sign = $('<a>').prop('href',images[activeImage][0])[0].search ? '&' : '?'; 758 url += sign + 'vq=hd720&wmode=opaque';
759 760 return $("<iframe>").prop({ scrolling:'no', frameborder:0, allowTransparency:true, src:url }).attr({webkitAllowFullScreen:true, mozallowfullscreen:true, allowFullScreen:true}); 761 } 762 763 // show the item's Title & Counter 764 function captionTextChange(){ 765 captionText.off(transitionend).removeClass('change'); 766 // change caption's text 767 if( options.counter ){ 768 try{ 769 var value = options.counter.replace('A', activeImage + 1).replace('B', images.length); 770 } 771 // if, for some reason, the above has failed from a bad "counter" value, reset and retry 772 catch(err){ 773 options.counter = '(A/B)'; 774 captionTextChange(); 775 } 776 caption.find('.counter').text(value); 777 } 778 var title = images[activeImage][1]; 779 // if there's a callback for this point: 780 if( typeof options.getTitle == "function") 781 title = options.getTitle(imageLinks[activeImage]); 782 783 if( options.title ) 784 caption.find('.title').html('<span>' + title + '</span>'); 785 } 786 787 // Handles the history states when changing images 788 var history = { 789 save : function(){ 790 // only save to history urls which are not already in the hash 791 if('pushState' in window.history && decodeURIComponent(window.location.hash.slice(1)) != activeURL && options.history ){ 792 window.history.pushState( 'photobox', doc.title + '-' + images[activeImage][1], window.location.pathname + window.location.search + '#' + encodeURIComponent(activeURL) ); 793 } 794 }, 795 load : function(){ 796 if( options && !options.history ) return false; 797 var hash = decodeURIComponent( window.location.hash.slice(1) ), i, j; 798 if( !hash && overlay.hasClass('show') ) 799 close(); 800 801 $('a[href="' + hash + '"]').trigger('click.photobox'); 802 }, 803 clear : function(){ 804 if( options.history && 'pushState' in window.history ) 805 window.history.pushState('photobox', doc.title, window.location.pathname + window.location.search); 806 } 807 }; 808 809 // Add Photobox special `onpopstate` to the `onpopstate` function 810 window.onpopstate = (function(){ 811 var cached = window.onpopstate; 812 return function(event){ 813 cached && cached.apply(this, arguments); 814 if( event.state == 'photobox' ) 815 history.load(); 816 } 817 })(); 818 819 // handles all image loading error (if image is dead) 820 function imageError(){ 821 overlay.addClass('error'); 822 image[0].src = blankImg; // set the source to a blank image 823 preload.onerror = null; 824 } 825 826 function getImages() { 827 return images; 828 } 829 830 // Shows the content (image/video) on the screen 831 function showContent(firstTime){ 832 var out, showSaftyTimer; 833 showSaftyTimer = setTimeout(show, 2000); 834 835 // hides the current image and prepare ground for an image change 836 pbLoader.fadeOut(300, function(){ 837 overlay.removeClass("pbLoading"); 838 pbLoader.removeAttr('style'); 839 }); 840 overlay.addClass('hide'); 841 842 image.add(video).removeAttr('style').removeClass('zoomable'); // while transitioning an image, do not apply the 'zoomable' class 843 844 // check which element needs to transition-out: 845 if( !firstTime && imageLinks[lastActive].rel == 'video' ){ 846 out = video; 847 image.addClass('prepare'); 848 } 849 else 850 out = image; 851 852 if( firstTime || isOldIE ) 853 show(); 854 else 855 out.on(transitionend, show); 856 857 // in case the 'transitionend' didn't fire 858 // after hiding the last seen image, show the new one 859 function show(){ 860 clearTimeout(showSaftyTimer); 861 out.off(transitionend).css({'transition':'none'}); 862 overlay.removeClass('video'); 863 if( activeType == 'video' ){ 864 image[0].src = blankImg; 865 video.addClass('prepare'); 866 overlay.addClass('video'); 867 } 868 else 869 image.prop({ 'src':activeURL, 'class':'prepare' }); 870 871 // filthy hack for the transitionend event, but cannot work without it: 872 setTimeout(function(){ 873 image.add(video).removeAttr('style').removeClass('prepare'); 874 overlay.removeClass('hide next prev'); 875 setTimeout(function(){ 876 image.add(video).on(transitionend, showDone); 877 if(isOldIE) showDone(); // IE9 and below don't support transitionEnd... 878 }, 0); 879 },50); 880 } 881 } 882 883 // a callback whenever a transition of an image or a video is done 884 function showDone(){ 885 image.add(video).off(transitionend).addClass('zoomable'); 886 if( activeType == 'video' ) 887 video.removeClass('hide'); 888 else{ 889 autoplayBtn && options.autoplay && APControl.play(); 890 } 891 if(photobox && typeof photobox.callback == 'function' ) 892 photobox.callback.apply(imageLinks[activeImage]); 893 } 894 895 function scrollZoom(e, deltaY, deltaX){ 896 if( deltaX ) return false;
897 898 if( activeType == 'video' ){ 899 var zoomLevel = video.data('zoom') || 1; 900 zoomLevel += (deltaY / 10); 901 if( zoomLevel < 0.5 ) 902 return false; 903 904 video.data('zoom', zoomLevel).css({width:624*zoomLevel, height:351*zoomLevel}); 905 } 906 else{ 907 var zoomLevel = image.data('zoom') || 1, 908 getSize = image[0].getBoundingClientRect(); 909 910 zoomLevel += (deltaY / 10); 911 912 if( zoomLevel < 0.1 ) 913 zoomLevel = 0.1; 914 915 raf(function() { 916 image.data('zoom', zoomLevel).css({'transform':'scale('+ zoomLevel +')'}); 917 }); 918 919 // check if image (by mouse) movement should take effect (if image is larger than the window 920 if( getSize.height > docElm.clientHeight || getSize.width > docElm.clientWidth ){ 921 $(doc).on('mousemove.photobox', imageReposition); 922 } 923 else{ 924 $(doc).off('mousemove.photobox'); 925 image[0].style[transformOrigin] = '50% 50%'; 926 } 927 } 928 return false; 929 } 930 931 function thumbsResize(e, delta){ 932 e.preventDefault(); 933 e.stopPropagation(); // stop the event from bubbling up to the Overlay and enlarge the content itself 934 var thumbList = photobox.thumbsList, h; 935 thumbList.css('height', thumbList[0].clientHeight + (delta * 10) ); 936 h = caption[0].clientHeight / 2; 937 wrapper[0].style.cssText = "margin-top: -"+ h +"px; padding: "+ h +"px 0;"; 938 thumbs.hide().show(0); 939 //thumbs.trigger('mouseenter').trigger('mousemove'); 940 } 941 942 // moves the image around during zoom mode on mousemove event 943 function imageReposition(e){ 944 var y = (e.clientY / docElm.clientHeight) * (docElm.clientHeight + 200) - 100, // extend the range of the Y axis by 100 each side 945 yDelta = y / docElm.clientHeight * 100, 946 xDelta = e.clientX / docElm.clientWidth * 100, 947 origin = xDelta.toFixed(2)+'% ' + yDelta.toFixed(2) +'%'; 948 949 raf(function() { 950 image[0].style[transformOrigin] = origin; 951 }); 952 } 953 954 function stop(){ 955 clearTimeout(APControl.autoPlayTimer); 956 $(doc).off('mousemove.photobox'); 957 preload.onload = function(){}; 958 preload.src = preloadPrev.src = preloadNext.src = activeURL; 959 } 960 961 function close(){ 962 if( !overlay.hasClass('show') ) 963 return false; 964 965 stop(); 966 video.find('iframe').prop('src','').empty(); 967 Photobox.prototype.setup(); 968 history.clear(); 969 970 overlay.removeClass('on video').addClass('hide'); 971 972 image.on(transitionend, hide); 973 isOldIE && hide(); 974 975 // the "photobox" instance might be needed for async transitionEnd functions, so give it some time before clearing it 976 setTimeout(function(){ 977 photobox = null; 978 },1000); 979 980 function hide(){ 981 if( overlay[0].className == '' ) return; // if already hidden 982 overlay.removeClass('show hide error pbLoading'); 983 image.removeAttr('class').removeAttr('style').off().data('zoom',1); 984 caption.find('.title').empty(); 985 986 if(noPointerEvents) // pointer-events lack support in IE, so just hide the overlay 987 setTimeout(function(){ overlay.hide(); }, 200); 988 989 options.hideFlash && $('iframe, object, embed').css('visibility', 'visible'); 990 } 991 992 // fall-back if the 'transitionend' event didn't fire 993 setTimeout(hide, 500); 994 // callback after closing the gallery 995 if( typeof options.afterClose === 'function' ) 996 options.afterClose(overlay); 997 } 998 999 /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) 1000 * Licensed under the MIT License (LICENSE.txt). 1001 * 1002 * Version: 3.1.11 1003 * 1004 * Requires: jQuery 1.2.2+ 1005 */ 1006 !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports&&typeof module!='undefined'&&module.exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1
vendor: 2,738 bytes, lines 1006-1022
1006),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.11",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b)["offsetParent"in a.fn?"offsetParent":"parent"]();return c.length||(c=a("body")),parseInt(c.css("fontSize"),10)},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); 1007 1008 ////////////// ON DOCUMENT READY ///////////////// 1009 $(doc).ready(prepareDOM); 1010 1011 // Expose: 1012 window._photobox = { 1013 DOM : { 1014 overlay : overlay 1015 }, 1016 close : close, 1017 getImages: getImages, 1018 changeImage: changeImage, 1019 history : history, 1020 defaults : defaults 1021 }; 1022})(jQuery, document, window);
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.